过完年开始刷回溯算法,寒假在家时间多点,争取每天多刷点题
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。通常是解决复杂的题。
回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
模板
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}
77. 组合
重点在调用递归的代码,本质上是取代未知层数的循环
码题时直接add了path,最终 res 列表中所有的元素都会指向同一个对象,会导致结果不正确。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtrack(n,k,1);return res;}void backtrack(int n, int k, int start) {if (path.size() == k) {res.add(path);path.clear();return;}for (int i = start; i <= n; i++) {path.add(i);backtrack(n,k,i+1);path.removeLast();}}
}
由于后面有回溯的操作,所以根本不用清空path,每次把path的副本添加到结果列表中。
ArrayList使用removeLast充当pop,new ArrayList<>(path)创建副本。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtrack(n,k,1);return res;}void backtrack(int n, int k, int start) {if (path.size() == k) {res.add(new ArrayList<>(path));return;}for (int i = start; i <= n; i++) {path.add(i);backtrack(n,k,i+1);path.removeLast();}}
}
77. 组合优化
采用剪枝操作减少不必要的穷举:在当前的组合路径 path
中已经有了 path.size()
个元素时,还需要选择 k - path.size()
个元素。当 i 的取值为 n - (k - path.size()) + 1
时,即使后面的数字全都选上,也凑不够 k 个元素,所以循环就可以结束了,不需要再继续遍历了。因此,使用 n - (k - path.size()) + 1
作为循环的结束条件,可以避免不必要的遍历,提高效率。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtrack(n,k,1);return res;}void backtrack(int n, int k, int start) {if (path.size() == k) {res.add(new ArrayList<>(path));return;}for (int i = start; i <=n - (k - path.size()) + 1; i++) {path.add(i);backtrack(n,k,i+1);path.removeLast();}}
}
216.组合总和III
看完发现是k数之和,可以用前面的题魔改(在path满了的时候顺手判断下和是否符合要求),并且已经知道了递归树宽度为9。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {backtrack(n,k,1,0);return res;}void backtrack(int target, int k, int start, int sum) {if (path.size() == k) {if (sum == target) res.add(new ArrayList<>(path));return;}for (int i = start; i <= 9; i++) {path.add(i);sum += i;backtrack(target, k, i+1,sum);path.removeLast();sum -= i;}}
}
这道题的剪枝有两点,首先可以模仿组合优化把for循环里的i边界改了,同时如果发现sum比target大后可以直接return。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {backtrack(n,k,1,0);return res;}void backtrack(int target, int k, int start, int sum) {if (sum > target) return;if (path.size() == k) {if (sum == target) res.add(new ArrayList<>(path));return;}for (int i = start; i <= 9 - (k - path.size()) + 1; i++) {path.add(i);sum += i;backtrack(target, k, i+1,sum);path.removeLast();sum -= i;}}
}