刷题的第二十天,希望自己能够不断坚持下去,迎来蜕变。😀😀😀
刷题语言:C++
Day20 任务
● 理论基础
● 77. 组合
1 回溯算法理论基础
1.1 回溯法
回溯法是一种搜索的方式,是递归的副产品(只要有递归,就会有回溯)
回溯函数就是递归函数
1.2 回溯法的效率
回溯的本质是穷举,穷举所有可能,选出想要的答案。想让回溯法高效一些,可以加一些剪枝的操作
,但也改不了回溯法就是穷举的本质
1.3 回溯法能解决的问题
(1)组合:N个数里面按一定规则找出k个数的集合
(2)切割:一个字符串按一定规则有几种切割方式
(3)子集:一个N个数的集合里有多少符合条件的子集
(4)排列:N个数按一定规则全排列,有几种排列方式
(5)棋盘:N皇后,解数独
组合是不强调元素顺序的,排列是强调元素顺序。
理解回溯法
回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度,因为递归有终止条件,所以必然是一棵高度有限的N叉树
1.4 回溯法模板
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果;}
}
2 组合
77. 组合
思路:
回溯法解决这种k层for循环嵌套的问题
把回溯法的搜索过程抽象为树形结构
每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围。
图中可以发现n相当于树的宽度,k相当于树的深度
回溯法
(1)递归函数的返回值以及参数
定义两个全局变量:存放符合条件的单一结果、存放符合条件结果的集合
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
返回值:void
参数:n,k,startIndex
startIndex 就是防止出现重复的组合
在集合[1,2,3,4]取1之后,下一层递归,在[2,3,4]中取数了,靠的就是startIndex:记录下一层递归,搜索的起始位置
void backtracking(int n, int k, int startIndex)
(2)回溯函数终止条件
到叶子节点,就是path数组大小为k,说明找到了一个子集大小为k的组合。此时用result二维数组,把path保存起来,并终止本层递归
if (path.size() == k) {result.push_back(path);return;
}
(3)单层搜索的过程
回溯法的搜索过程就是一个树型结构的遍历过程
for (int i = startIndex; i <= n; i++) {// 树的横向遍历path.push_back(i);// 处理节点backtracking(n, k, i + 1);// 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始path.pop_back();// 回溯,撤销处理的节点
}
C++:
class Solution {
public:vector<vector<int>> result;// 存放符合条件结果的集合vector<int> path;// 用来存放符合条件结果void backtracking(int n, int k, int startIndex) {if (path.size() == k) {result.push_back(path);return;}for (int i = startIndex; i <= n; i++) {path.push_back(i);// 处理节点backtracking(n, k, i + 1);// 递归path.pop_back();// 回溯,撤销处理的节点}}vector<vector<int>> combine(int n, int k) {result.clear();path.clear();backtracking(n, k, 1);return result;}
};
时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)
空间复杂度: O ( n ) O(n) O(n)
组合问题是回溯法解决的经典问题
3 剪枝优化
n = 4,k = 4,第一层for循环的时候,从元素2开始的遍历都没有意义了。
图中每一个节点代表本层的一个for循环,每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历
以剪枝的地方就在递归中每一层的for循环所选择的起始位置
优化:
(1)已经选择的元素个数:path.size()
(2)还需要的元素个数:k - path.size()
(3)在集合n中至多从该起始位置:n - (k -path.size()) + 1,开始遍历
有个+1是因为包括起始位置,我们要是一个左闭的集合
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++)// i为本次搜索的起始位置
C++:
优化后的代码
class Solution {
public:vector<vector<int>> result;vector<int> path;void backtracking(int n, int k, int startIndex) {if (path.size() == k) {result.push_back(path);return;}for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {path.push_back(i);backtracking(n, k, i + 1);path.pop_back();}}vector<vector<int>> combine(int n, int k) {result.clear();path.clear();backtracking(n, k, 1);return result;}
};
鼓励坚持二十一天的自己😀😀😀