回溯算法
回溯本身其实就是递归算法,是一种暴力的算法,但是有的题本身就难以解决,这种时候回溯算法也算一种优解了
回溯算法解决什么问题:
- 组合问题 :组合
- 切割问题
- 子集问题
- 排列问题 :全排列
- 棋盘问题 :n皇后问题,数独问题
这里附一个回溯算法的模板
//backtraking()
//回溯算法模板
let backtracking = function() {
//终止递归
if(终止条件){
//收集结果
return 结果;
}
for(let 集合的元素 of 集合){
//处理节点()
//递归函数
//回溯操作(撤销处理节点的情况)
}
return;
}
1. 组合问题
组合
**给定两个整数 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
第一版 单回溯算法(无优化)
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
let backtracking = function(n,k,starIndex,path,result) {
//当path记录路径中的 数据数目 等于题目要求的k时 将结果压入result结果集, 并终止该递归
if(k === path.length){
result.push([...path]);
return
}
for(let i = starIndex; i <= n; i++){
path.push(i); //记录路径
backtracking(n,k,i + 1, path,result); //递归,深度+1
path.pop(); //回溯操作
}
}
let combine = function(n,k) {
let path = new Array();
let result = new Array();
backtracking(n,k,1,path,result);
// console.log(result)
return result;
}
组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都 互不相同
1 <= target <= 500
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
let result = []
let path = []
const backtracking = (candidates,deep,path,sum,target) => {
if(sum == target){
result.push([...path]);
return
}
if(sum > target){
return
}
for(let i = deep ; i < candidates.length ; i++){
path.push(candidates[i])
sum += candidates[i]
backtracking(candidates,i,path,sum,target);
sum -= candidates[i]
path.pop()
}
}
backtracking(candidates,0,path,0,target);
return result;
};
组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function(candidates, target) {
let result = [];
let len = candidates.length;
let vis = new Array(len).fill(false);
const backtracking = (path,result,vis,deep,target,sum,num) => {
if(sum == target){
result.push([...path])
return
}
if(sum > target){
return
}
for(let i = deep; i < len ;i++){
if(!vis[i]){
if(i>0 && num[i] == num[i-1] && !vis[i-1]) continue; //这个条件可以筛去重复数据
path.push(num[i])
vis[i] = true;
sum += num[i];
backtracking(path,result,vis,i , target , sum,num)
sum -= num[i];
vis[i] = false;
path.pop();
}
}
}
candidates.sort((x,y) => x-y)
backtracking([],result,vis,0,target,0,candidates)
return result
};
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
提示:
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if(digits == "") return []; //特殊情况
let result = [];
let len = digits.length;
let arr = [
['a','b','c'],
['d','e','f'],
['g','h','i'],
['j','k','l'],
['m','n','o'],
['p','q','r','s'],
['t','u','v'],
['w','x','y','z']
]
let nums = [];
for(let i = 0; i < len; i++){
nums.push(parseInt(digits.charAt(i))-2);
}
const backtracking = (path, deep, result,arr,nums) => {
let index = nums[deep];
if(deep == len){
result.push(path.join(''));
return
}
for(let i = 0 ; i < arr[index].length; i++){
path.push(arr[index][i])
backtracking(path,deep+1,result,arr,nums)
path.pop()
}
}
backtracking([],0,result,arr,nums)
return result;
};
2.排列问题
全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
/**
*
* @param {number[]} nums
* @returns {number[][]}
*/
var permute = function(nums) {
let len = nums.length;
let result = [];
let vis = new Array(len).fill(false);
/**
*
* @param {number[]} nums 数据源
* @param {number} len 数据源长度
* @param {number} deep 递归深度
* @param {number[]} path 记录的路径
* @param {boolean[]} vis 访问标记
* @returns
*/
const backtracking = (nums, len, deep, path, vis) => {
if(deep === len) {
result.push([...path]);
return;
}
for(let i = 0; i < len; i++) {
if(!vis[i]) {
path.push(nums[i]);
vis[i] = true;
backtracking(nums, len, deep + 1, path, vis);
vis[i] = false;
path.pop();
}
}
}
backtracking(nums, len, 0, [], vis);
return result;
};
console.log(permute([1,2,3]))
全排列Ⅱ()
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
与上一题相识,但是多了一步判断操作,优化了递归的路径,以及必须保证数据是有序的,所以加了一步排序
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
let len = nums.length;
let result = [];
let vis = new Array(len).fill(false);
/**
*
* @param {number[]} nums 数据源
* @param {number} len 数据源长度
* @param {number} deep 递归深度
* @param {number[]} path 记录的路径
* @param {boolean[]} vis 数据源访问标记
* @returns
*/
const backtracking = (nums, len, deep, path, vis) => {
if(deep === len) {
result.push([...path]);
return;
}
for(let i = 0; i < len; i++) {
if(!vis[i]) {
//保证
if(i > 0 && nums[i] == nums[i-1] && vis[i-1]) continue;
path.push(nums[i]);
vis[i] = true;
backtracking(nums, len, deep + 1, path, vis);
vis[i] = false;
path.pop();
}
}
}
//对nums排序
nums.sort((x,y)=>x-y);
backtracking(nums, len, 0,s [], vis);
return result;
};
测试数据集
console.log(deep,path,vis,result)
递归深度 路径 数据源访问标记 结果集
nums = [1,2,3]
0 [] [ false, false, false ] []
1 [ 1 ] [ true, false, false ] []
2 [ 1, 2 ] [ true, true, false ] []
3 [ 1, 2, 3 ] [ true, true, true ] []
2 [ 1, 3 ] [ true, false, true ] [ [ 1, 2, 3 ] ]
3 [ 1, 3, 2 ] [ true, true, true ] [ [ 1, 2, 3 ] ]
1 [ 2 ] [ false, true, false ] [ [ 1, 2, 3 ], [ 1, 3, 2 ] ]
2 [ 2, 1 ] [ true, true, false ] [ [ 1, 2, 3 ], [ 1, 3, 2 ] ]
3 [ 2, 1, 3 ] [ true, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ] ]
2 [ 2, 3 ] [ false, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ] ]
3 [ 2, 3, 1 ] [ true, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ] ]
1 [ 3 ] [ false, false, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ] ]
2 [ 3, 1 ] [ true, false, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ] ]
3 [ 3, 1, 2 ] [ true, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ] ]
2 [ 3, 2 ] [ false, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ] ]
3 [ 3, 2, 1 ] [ true, true, true ] [ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ] ]
[ [ 1, 2, 3 ], [ 1, 3, 2 ], [ 2, 1, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ], [ 3, 2, 1 ] ]
nums = [1,1,3]
0 [] [ false, false, false ] []
1 [ 1 ] [ true, false, false ] []
2 [ 1, 3 ] [ true, false, true ] []
1 [ 1 ] [ false, true, false ] []
2 [ 1, 1 ] [ true, true, false ] []
3 [ 1, 1, 3 ] [ true, true, true ] []
2 [ 1, 3 ] [ false, true, true ] [ [ 1, 1, 3 ] ]
3 [ 1, 3, 1 ] [ true, true, true ] [ [ 1, 1, 3 ] ]
1 [ 3 ] [ false, false, true ] [ [ 1, 1, 3 ], [ 1, 3, 1 ] ]
2 [ 3, 1 ] [ true, false, true ] [ [ 1, 1, 3 ], [ 1, 3, 1 ] ]
2 [ 3, 1 ] [ false, true, true ] [ [ 1, 1, 3 ], [ 1, 3, 1 ] ]
3 [ 3, 1, 1 ] [ true, true, true ] [ [ 1, 1, 3 ], [ 1, 3, 1 ] ]
[ [ 1, 1, 3 ], [ 1, 3, 1 ], [ 3, 1, 1 ] ]
特殊情况:三个连续的重复数字
nums = [1,1,1,2]
0 [] [ false, false, false, false ] []
1 [ 1 ] [ true, false, false, false ] []
2 [ 1, 1 ] [ true, false, true, false ] []
3 [ 1, 1, 2 ] [ true, false, true, true ] []
2 [ 1, 2 ] [ true, false, false, true ] []
3 [ 1, 2, 1 ] [ true, false, true, true ] []
1 [ 1 ] [ false, true, false, false ] []
2 [ 1, 1 ] [ true, true, false, false ] []
3 [ 1, 1, 2 ] [ true, true, false, true ] []
2 [ 1, 2 ] [ false, true, false, true ] []
3 [ 1, 2, 1 ] [ true, true, false, true ] []
1 [ 1 ] [ false, false, true, false ] []
2 [ 1, 1 ] [ true, false, true, false ] []
3 [ 1, 1, 2 ] [ true, false, true, true ] []
2 [ 1, 1 ] [ false, true, true, false ] []
3 [ 1, 1, 1 ] [ true, true, true, false ] []
4 [ 1, 1, 1, 2 ] [ true, true, true, true ] []
3 [ 1, 1, 2 ] [ false, true, true, true ] [ [ 1, 1, 1, 2 ] ]
4 [ 1, 1, 2, 1 ] [ true, true, true, true ] [ [ 1, 1, 1, 2 ] ]
2 [ 1, 2 ] [ false, false, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ] ]
3 [ 1, 2, 1 ] [ true, false, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ] ]
3 [ 1, 2, 1 ] [ false, true, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ] ]
4 [ 1, 2, 1, 1 ] [ true, true, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ] ]
1 [ 2 ] [ false, false, false, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
2 [ 2, 1 ] [ true, false, false, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
3 [ 2, 1, 1 ] [ true, false, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
2 [ 2, 1 ] [ false, true, false, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
3 [ 2, 1, 1 ] [ true, true, false, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
2 [ 2, 1 ] [ false, false, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
3 [ 2, 1, 1 ] [ true, false, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
3 [ 2, 1, 1 ] [ false, true, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
4 [ 2, 1, 1, 1 ] [ true, true, true, true ] [ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ] ]
[ [ 1, 1, 1, 2 ], [ 1, 1, 2, 1 ], [ 1, 2, 1, 1 ], [ 2, 1, 1, 1 ] ]