今天开始刷回溯算法题。其实之前刷二叉树的题的时候,有接触过回溯。而回溯算法,也可以转化为一个多叉树全遍历问题(可以剪枝),本质其实是一种暴力全搜索,穷举所有可能返回答案。
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
回溯算法的大致框架可以概括为:
List<Value> result;
void backtracking(参数) // 参数一般和路径、选择列表有关
if (终止条件) {
result.add(路径); // 存放结果
return;
}
for (选择:选择列表) { // 选择列表:本层集合中元素(树中节点孩子的数量就是集合的大小)
处理节点(做选择);
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果(撤销选择);
}
给定两个整数 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]]
提示:
1 <= n <= 20
1 <= k <= n
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
private void backtracking(int n, int k, int startIndex) {
if (path.size() == k) { // 终止条件,路径大小等于k
result.add(new ArrayList<>(path)); // result添加路径
return;
}
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
path.add(i); // 做选择
backtracking(n, k, i + 1); // 递归
path.removeLast(); // 撤销选择
}
}
}
直接套模版就行了。这里路径的遍历用到startIndex这个参数,是路径遍历到的节点,也是递归遍历的起点。每次从集合中选取元素,可选择的范围随着选择的进行而收缩,而startIndex则用于调整可选择的范围。同时还可以剪枝,选取做选择的范围在剪枝前是startIndex到n之间,但我们可以发现取到大于 n - (k - 路径大小)+ 1 时,收集到的结果已经没有意义了,可以剪枝减掉。