回溯理论基础
回溯时递归的副产品,只要有递归就会有回溯
回溯的效率
回溯的本质是穷举,穷举所有可能,然后选出想要的答案l如果想更高效一些,可以加一些剪枝操作
回溯法解决的问题
-
组合问题:N 个数里按一定规划找出 k 个数的集合
-
切割问题:一个字符串按一定规则有几种切割方式
-
子集问题:一个 N 个数的集合里有多少符合条件的子集
-
排列问题:N 个数按一定规则全排列,有几种排列方式
-
棋盘问题:N 皇后,解数独等
组合是不强调元素顺序的,排列时强调元素顺序
例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合
组合无序,排列有序
理解回溯法
所有回溯法解决的问题都可以抽象为树形结构
回溯法解决在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度
递归有终止条件,所以必然是一棵高度有限的树(N 叉树)
回溯法模板
回溯三部曲:
-
回溯函数模板返回值以及参数
- 函数习惯命名 backtracking
- 返回值一般为 void
- 参数一般是先写逻辑,然后需要什么再填
-
回溯函数终止条件
- 存放结果并结束本层递归时
-
回溯搜索的遍历过程
-
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成树的深度
-
注意图中举例集合大小和孩子数量是相等的
-
回溯函数遍历过程伪代码
-
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 }
-
-
整体模板如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77.组合
题目
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
提示:
-
1 <= n <= 20
-
1 <= k <= n
思路
将组合问题抽象为如下树形结构
可以看出这棵树,一开始集合是 1,2,3,4,从左向右取数,取过的数不再重复取
每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围
图中 n 为树的宽度,k 为树的深度
每次搜索到了叶子节点,就找到了一个结果
回溯法三部曲:
-
递归函数的返回值以及参数
- 定义两个全局变量,一个存放符合条件的单一结果,一个存放符合条件结果的集合(题目要求)
- n 和 k
- int 型 startIndex,记录本层递归中,集合从哪里开始遍历,防止出现重复的组合。如下图红线,在集合 [1,2,3,4] 取 1 后,下一层递归就要在 [2,3,4] 中取数,如何知道从 [2,3,4] 取数靠的就是 startIndex
-
回溯函数终止条件
- path 数组的大小如果等于 k,说明找到了一个子集大小为 k 的组合,用 result 保存,并终止本层递归
-
单层搜索过程
- for 循环用来横向遍历,递归的过程是纵向遍历
-
-
for 每次从 startIndex 开始遍历,用 path 保存取到的节点 i
-
- for 循环用来横向遍历,递归的过程是纵向遍历
剪枝
遍历范围优化,举例 n = 4,k = 4,那么第一层 for 循环时,从元素 2 开始的遍历都没有意义了
可以剪枝的地方就在递归中每一层 for 循环所选择的起始位置
如果 for 循环选择的起始位置之后的元素个数已经不足需要的元素个数,就没有必要搜索
代码实现
class Solution {
private:
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();
}
}
public:
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};