感觉遇到求有哪些结果的穷举, 就可以用到回溯
回溯: 有递归就有回溯
回溯的本质是穷举, 不好理解也不高效
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
回溯法解决的问题都可以抽象为树形结构
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77. 组合
难度 3
解法 1 回溯
var combine = function(n, k) {
let result = [], path = [];
const combineHelper = (n,k,startIndex) => {
if(path.length ===k){
result.push([...path]);
return;
}
for(let i = startIndex;i<=n-(k-path.length)+1;i++){
path.push(i);
combineHelper(n,k,i+1);
path.pop();
}
}
combineHelper(n,k,1);
return result;
}
复杂度
思路
n是树的宽度, k限制树的深度, path.length和 k 相等时就终止
216. 组合总和 III
难度 4
解法 1 回溯
var combinationSum3 = function(k, n) {
let res = [], path = [];
const combineHelp = (k,n,startIndex,sum) => {
if(sum>n) return;//可不要, 这里起到剪枝作用
if(path.length===k){
if(sum===n) res.push([...path]);
return;
}
for(let i = startIndex;i<=9-(k-path.length)+1;i++){
path.push(i);
combineHelp(k,n,i+1,sum+i);
path.pop();
}
}
combineHelp(k,n,1,0);
return res;
};
复杂度
思路
没有上个好理解
回溯的条件是需要什么就加什么
17. 电话号码的字母组合
难度
解法
复杂度
思路
39. 组合总和
难度 3
解法 1 回溯
var combinationSum = function(candidates, target) {
let res = [], path = [];
const backtracking = (candidates,target,startIndex,sum) => {
if(sum>target) return;
if(sum===target){
res.push([...path]);
return;
}
for(let i = startIndex;i<candidates.length;i++){
path.push(candidates[i]);
backtracking(candidates,target,i,sum+candidates[i])
path.pop();
}
}
backtracking(candidates,target,0,0);
return res;
};
复杂度
思路
目前多组合排列大同小异, for 循环的限制条件
如果是和求和有关, if > 可以起到剪枝作用
40. 组合总和 II
难度 3
解法 1 回溯
var combinationSum2 = function(candidates, target) {
candidates.sort((a,b)=>a-b);
let res = [], path = [];
const backtracking = (candidates,target,startIndex, sum) =>{
if(sum>target) return;
if(sum===target){
res.push([...path]);
return;
}
for(let i = startIndex;i<candidates.length;i++){
if(i>startIndex&&candidates[i]===candidates[i-1]){
continue;
}
path.push(candidates[i]);
backtracking(candidates,target,i+1,sum+candidates[i]);
path.pop();
}
}
backtracking(candidates,target,0,0);
return res;
};
复杂度
思路
比上一题增加了去重的成分, 难点是i>startindex, 这里是如果跳过重复的就不再跳的意思, 因为相同的数字能用, 但是每个数字只能用一次(数组有排列过)
131. 分割回文串
难度
解法
复杂度
思路
93. 复原 IP 地址
难度
解法
复杂度
思路
78. 子集
难度
解法
var subsets = function(nums) {
let res = [], path = [];
const backtrackting = (nums, startIndex) =>{
res.push([...path]);
for(let i = startIndex;i<nums.length;i++){
path.push(nums[i]);
backtrackting(nums,i+1);
path.pop();
}
}
backtrackting(nums,0);
return res;
};
复杂度
思路
i+1貌似只有能重复使用才不加
90. 子集 II
难度 3
解法 1 回溯
var subsetsWithDup = function(nums) {
let res = [], path = [];
nums.sort((a,b)=>a-b);
const backtrackting = (nums, startIndex) =>{
res.push([...path]);
for(let i = startIndex;i<nums.length;i++){
if(i>startIndex&&nums[i]===nums[i-1]) continue;
path.push(nums[i]);
backtrackting(nums,i+1);
path.pop();
}
}
backtrackting(nums,0);
return res;
};
复杂度
思路
去重一般是先升序, 再 for 循环中用 if 去掉
491. 递增子序列
难度 3
解法 1 回溯
var findSubsequences = function(nums) {
let res = [], path = [];
const backtracking = (nums, index) =>{
if(path.length>=2){
res.push([...path]);
}
const set = new Set();
for(let i = index;i<nums.length;i++){
if(nums[i]<path[path.length-1]||set.has(nums[i])) continue;
set.add(nums[i]);
path.push(nums[i]);
backtracking(nums,i+1);
path.pop();
}
}
backtracking(nums,0);
return res;
};
复杂度
思路
限制条件是至少两个, 值得注意的是等于这个数也属于递增
46. 全排列
难度 3
解法 1 回溯
var permute = function(nums) {
let res = [], path = [];
const backtracking = (nums,used)=> {
if(path.length===nums.length){
res.push([...path]);
return;
}
for(let i = 0;i<nums.length;i++){
if(used[i]) continue;
path.push(nums[i]);
used[i] = true;
backtracking(nums,used);
path.pop();
used[i] = false;
}
}
backtracking(nums,[]);
return res;
};
复杂度
思路
这一题和之前不同, [1,2]和[2,1]算不同集合, 所以用 used 存储是否使用过
- 每层都是从0开始搜索而不是startIndex
- 需要used数组记录path里都放了哪些元素了
47. 全排列 II
难度 3
解法 1 回溯
var permuteUnique = function(nums) {
nums.sort((a,b)=>a-b);
let res = [], path = [];
const backtracking = (nums, used) =>{
if(path.length===nums.length){
res.push([...path]);
return;
}
for(let i = 0;i<nums.length;i++){
if(!used[i-1]&&nums[i]===nums[i-1]) continue;
if(!used[i]){
path.push(nums[i]);
used[i] = true;
backtracking(nums,used);
path.pop();
used[i] = false;
}
}
}
backtracking(nums,[]);
return res;
};
复杂度
思路
去重的 if 条件要再思考
332. 重新安排行程
难度
解法
复杂度
思路
51. N 皇后
难度
解法
复杂度
思路
37. 解数独
难度
解法
复杂度