回 溯 算 法

回溯算法

回溯本身其实就是递归算法,是一种暴力的算法,但是有的题本身就难以解决,这种时候回溯算法也算一种优解了
回溯算法解决什么问题:

  1. 组合问题 :组合
  2. 切割问题
  3. 子集问题
  4. 排列问题 :全排列
  5. 棋盘问题 :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 ] ]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值