ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LeetCode刷题day44

2022-03-01 14:33:21  阅读:174  来源: 互联网

标签:digits 递归 示例 int 回溯 path day44 LeetCode 刷题


今日刷题重点—回溯

文章目录

77. 组合

题目描述

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

思路分析

直接的解法是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。

int n = 4;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        cout << i << " " << j << endl;
    }
}

n = 100, k = 3 那么就三层for循环,代码如下:

int n = 100;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        for (int u = j + 1; u <= n; n++) {
            cout << i << " " << j << " " << u << endl;
        }
    }
}

但是如果n为100,k为50,那就是50层for循环,无语住了…
于是,我们可以尝试使用回溯法,本质就是 循环+递归,从而实现多层循环
回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了。
由下图可知:元素的个数n等于树的宽度,k等于树的深度

在这里插入图片描述

算法设计

  • 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围。

  • 图中每次搜索到了叶子节点,我们就找到了一个结果。

  • 只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。

卡尔大佬的回溯法三部曲

  • 递归函数的返回值以及参数
    这里直接使用全局变量来操作,一个存放最终的结果集,一个存放符合条件的临时集合.这样我们也就不需要返回值了.
    参数:数据个数n,组合个数k 每次需要传入.另外也需要传入下次循环的起始位置,方便下一次其他数据进行组合.
vector<int> path;
vector<vector<int>> result;
void backtracking(int n,int k,int startIndex){
...
}
  • 回溯函数终止条件
    当path.size() == k了,则说明我们已经找到了大小为k的组合了. 结束递归
if(path.size()==k){
	result.push_back(path);
	return;//结束递归 
}
  • 单层搜索过程
    回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。
    在这里插入图片描述
    for循环每次从startIndex开始遍历,然后用path保存取到的节点i,递归回来之后进行回溯,继续循环.
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
    path.push_back(i); // 处理节点 
    backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
    path.pop_back(); // 回溯,撤销处理的节点
}

代码优化
可以剪枝的地方在递归中每一层的for循环条件处。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

  • 已经选择的元素个数:path.size();
  • 还需要的元素个数为: k - path.size();
  • 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
    为什么有个+1呢,因为包括起始位置。
    所以中间for循环的条件可以改造为:i <= n-(k-path.size())+1

参考代码

#include<bits/stdc++.h>
using namespace std;

vector<int> path;
vector<vector<int>> result;
void backtracking(int n,int k,int startIndex){
	if(path.size()==k){
		result.push_back(path);
		return;//结束递归 
	}
//	for(int i = startIndex; i <= n;i++){//未剪枝 
	for(int i = startIndex; i <= n-(k-path.size())+1;i++){	//剪枝 
		path.push_back(i);//存放i元素
		backtracking(n,k,i+1);//减小可选择返回,继续递归
		path.pop_back();//弹出当前元素,进行回溯 
	}
}

vector<vector<int>> combine(int n, int k) {
	backtracking(n,k,1);
	return result;
}

216.组合总和III

题目描述

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

思路分析

本题相对于上个题只是多个 之和为n的限制,其他的处理思路和上题类似
转为树形图就是:k相当于树的深度. 1-9 相当于树的宽度
在这里插入图片描述

下面开始我们熟悉的回溯三部曲

  • 确定递归参数和返回值
    递归参数:每次需要传入组合的位数 k,循环的下一个位置下标 index,当前组合数的和 sum,目标值 targetSum
  • 确定递归的结束条件
    当path.size()==k时结束递归.但同时满足sum == targetSum,当前组合数才被加入到结果集中
  • 确定单层递归的逻辑
    先循环,加入元素,之后继续递归,递归之后进行回溯,回溯完毕进入下一次循环

算法剪枝

  • 如果后序可选择的元素+当前组合数元素 小于 k,则没必要继续寻找了
  • 如果组合数的和已经大于targetSum,则没必要再去寻找了.

参考代码

#include<bits/stdc++.h>
using namespace std;

vector<int> path;
vector<vector<int>> result;

void backtracking(int k,int startIndex,int sum,int targetSum) {
	if(sum>targetSum) {//剪枝
		return;
	}
	if(path.size()==k) {
		if(sum==targetSum) {
			result.push_back(path);
		}
		return;//结束递归
	}
//	for(int i = startIndex; i <= n;i++){
	for(int i = startIndex; i <= 9-(k-path.size())+1; i++) { //此时还需要的元素大小   &&
		path.push_back(i);//存放i元素
		backtracking(k,i+1,sum+i,targetSum);//减小可选择返回,继续递归 这里返回的是i+1哦,和startIndex无关..
		path.pop_back();//弹出当前元素,进行回溯
	}
}

vector<vector<int>> combinationSum3(int k, int n) {
	backtracking(k,1,0,n);
	return result;
}

17. 电话号码的字母组合

题目描述

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

思路分析

输入:“23”,抽象为树形结构,如图所示:
在这里插入图片描述
图中可以看出遍历的深度就是输入的 字符串的长度,而叶子节点就是我们要收集的结果,宽度为 字符所对应的字符串长度 ,如2==>abc

回溯三部曲

  • 确定递归参数和返回值
    参数:需要传入输入的字符串digits,当前递归到的digits的index
    返回值:依旧定义两个个全局变量,一个是最终结果集,一个存放临时组合数.所以返回值为void 可以看出回溯类型的递归通常没有返回值
  • 确定递归结束条件
    如果index==digits.size(),则说明已经找到了满足的组合数,递归结束
  • 确定单层递归逻辑
    先从递归的index拿到对应的字符串,然后开始循环,递归,回溯…

参考代码

vector<string> result;
string s;
string  arr[10] = {
	"",//0
	"",//1
	"abc",//2
	"def",
	"ghi",
	"jkl",
	"mno",
	"pqrs",
	"tuv",
	"wxyz",
};

void backtracking(string& digits,int index) {//digits:记录输入的数字  ,index:遍历前面的数字
	if(digits.size()==index) {
		result.push_back(s);
		return;
	}
	int num = digits[index] - '0';
	string temp = arr[num];
	for(int i = 0; i < temp.size(); i++) { //每层遍历的都是digits中的元素  递归树的深度就是digits.size()大小
		s.push_back(temp[i]);
		backtracking(digits,index+1);
		s.pop_back();
	}
}
vector<string> letterCombinations(string digits) {//digits.size()==>k 循环次数
	if(digits=="") {//注意digits为空的情况 
		return result;
	}
	backtracking(digits,0);
	return result;
}

如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

标签:digits,递归,示例,int,回溯,path,day44,LeetCode,刷题
来源: https://blog.csdn.net/LXYDSF/article/details/123201307

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有