十二、leetcode - 回溯(JS)

276 阅读3分钟

46. 全排列 不含重复数字

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

输入: nums = [1,2,3]
输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
[ 1 ] 
[ 1, 2 ] 
[ 1, 2, 3 ] 
[ 1, 3 ] 
[ 1, 3, 2 ] 
[ 2 ] 
[ 2, 1 ] 
[ 2, 1, 3 ] 
[ 2, 3 ] 
[ 2, 3, 1 ] 
[ 3 ] 
[ 3, 1 ] 
[ 3, 1, 2 ] 
[ 3, 2 ] 
[ 3, 2, 1 ]
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    const res = [];
    const used = {};
    function dfs(path) {
        if(path.length === nums.length){
            res.push(path.slice());
            return res;
        }
        for(let i = 0; i < nums.length; i++) { // 横向循环
            if(used[i]) continue; // 纵向递归时去掉使用过的数字
            path.push(nums[i]);
            used[i] = true;
            console.info(path);
            dfs(path); // 纵向递归
            path.pop();
            used[i] = false;
        }
    }
    dfs([]); 
    return res;
};

47. 全排列 II 包含重复数字

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

输入: nums = [1,1,2]
输出: [ [1,1,2], [1,2,1], [2,1,1] ]
过滤掉首层未递归前的同层横向重复项(推荐这种,直接去掉了第二个分支)
这种属于从根上直接剪枝
!used[i-1]即used[i - 1] == false,说明同一树层nums[i - 1]使用过

[ 1 ] 
[ 1, 1 ] 
[ 1, 1, 2 ] 
[ 1, 2 ] 
[ 1, 2, 1 ] 
[ 2 ] 
[ 2, 1 ] 
[ 2, 1, 1 ]
过滤掉纵向递归中横向同层的重复
这种属于从枝叶上逐个深入剪枝,效率不如从根上剪枝好。理解起来也较为困难
used[i-1]即used[i - 1] == true,说明同一树支nums[i - 1]使用过

[ 1 ] 
[ 1, 2 ] 
[ 1 ] 
[ 1, 1 ] 
[ 1, 1, 2 ] 
[ 1, 2 ] 
[ 1, 2, 1 ] 
[ 2 ] 
[ 2, 1 ] 
[ 2, 1 ] 
[ 2, 1, 1 ]
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permuteUnique = function(nums) {
    let res = [];
    let used = {};
    // 先排序,以便过滤重复数字
    nums.sort((a,b)=>a-b); 
    const dfs = (path) => {
        if(path.length === nums.length){
            res.push(path.slice());
            return res;
        }
        for(let i = 0; i < nums.length; i++) {
            if(used[i]) continue;
            // 过滤掉首层未递归前的同层横向重复项(推荐这种)
            if(i>0 && nums[i] === nums[i-1] && !used[i-1]) continue;
            // if(i-1>=0 && nums[i] === nums[i-1] && !used[i-1]) continue;
            // 过滤掉纵向递归中横向同层的重复
            // if(i-1>=0 && nums[i] === nums[i-1] && used[i-1]) continue;
            path.push(nums[i]);
            console.info(path);
            used[i] = true;
            dfs(path);
            path.pop();
            used[i] = false;
        }
    }
    dfs([]);
    return res;
};

39. 组合总和

不含重复数字的数组,该数组中的数字可以无限制重复使用

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

输入: candidates = [2,3,6,7], target = 7
输出: [ [7], [2,2,3] ]
[ 2 ] 
[ 2, 2 ] 
[ 2, 2, 2 ] 
[ 2, 2, 2, 2 ] 
[ 2, 2, 2, 3 ] 
[ 2, 2, 2, 6 ] 
[ 2, 2, 2, 7 ] 
[ 2, 2, 3 ] 
[ 2, 2, 6 ] 
[ 2, 2, 7 ] 
[ 2, 3 ] 
[ 2, 3, 3 ] 
[ 2, 3, 6 ] 
[ 2, 3, 7 ] 
[ 2, 6 ] 
[ 2, 7 ] 
[ 3 ] 
[ 3, 3 ] 
[ 3, 3, 3 ] 
[ 3, 3, 6 ] 
[ 3, 3, 7 ] 
[ 3, 6 ] 
[ 3, 7 ] 
[ 6 ] 
[ 6, 6 ] 
[ 6, 7 ] 
[ 7 ]
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    let res = [];
    const dfs = (start, temp, sum) => {
        if(sum >= target){
            if(sum === target){
                res.push(temp.slice());
            }
            return;
        }
        for(let i = start; i < candidates.length; i++){
            temp.push(candidates[i]);
            dfs(i, temp, sum + candidates[i]);
            temp.pop();
        }
    }
    dfs(0,[],0);
    return res;
};

40. 组合总和 II

包含重复数字的数组,该数组中的数字在每个组合中只能使用一次

需要重点理解:我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。 

输入: candidates = [2,3,6,7], target = 7
输出: [ [7] ]
dfs(i + 1, temp, sum + candidates[i]);语句中
i+1保证candidates中的每个数字在每个组合中只能使用一次

[ 2 ] 
[ 2, 3 ] 
[ 2, 3, 6 ] 
[ 2, 3, 7 ] 
[ 2, 6 ] 
[ 2, 7 ] 
[ 3 ] 
[ 3, 6 ] 
[ 3, 7 ] 
[ 6 ] 
[ 6, 7 ] 
[ 7 ]
输入: candidates = [2,5,2,1,2], target = 5,
输出: [ [1,2,2], [5] ]
需要先排序:[1,2,2,2,5]

不去除横向同层重复项
[ 1 ] [ 1, 2 ] [ 1, 2, 2 ] [ 1, 2, 2 ] [ 1, 2, 5 ] 
[ 1, 2 ] [ 1, 2, 2 ] [ 1, 2, 5 ] 
[ 1, 2 ] [ 1, 2, 5 ] [ 1, 5 ] 
[ 2 ] [ 2, 2 ] [ 2, 2, 2 ] [ 2, 2, 5 ] [ 2, 2 ] [ 2, 2, 5 ] [ 2, 5 ] 
[ 2 ] [ 2, 2 ] [ 2, 2, 5 ] [ 2, 5 ] 
[ 2 ] [ 2, 5 ] 
[ 5 ]

去除横向同层重复项
[ 1 ] [ 1, 2 ] [ 1, 2, 2 ] [ 1, 2, 5 ] [ 1, 5 ] 
[ 2 ] [ 2, 2 ] [ 2, 2, 2 ] [ 2, 2, 5 ] [ 2, 5 ] 
[ 5 ]
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum2 = function(candidates, target) {
    let res = [];
    candidates.sort((a, b) => a - b);
    const dfs = (start,temp,sum) => {
        // 和大于等于目标值时,后面的分支已经不可能满足结果条件,剪枝返回
        if(sum >= target){
            // 和等于目标值时,记录目标值
            if(sum === target){
                res.push(temp.slice());
            }
            return;
        }
        for(let i = start; i < candidates.length; i++){
            // 去除横向同层重复项
            if(i > start && candidates[i] === candidates[i-1]) continue;
            temp.push(candidates[i]);
            // i+1保证candidates中的每个数字在每个组合中只能使用一次
            dfs(i + 1, temp, sum + candidates[i]);
            temp.pop();
        }
    }
    dfs(0,[],0);
    return res;
};

216. 组合总和 III

范围为[1, 9],固定每个组合长度为k,target为n

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。 解集不能包含重复的组合。

输入: k = 2, n = 7
输出: [[1,6],[2,5],[3,4]]
目前的未作剪枝处理,所有的组合都打印出来了。剪枝待更新。。。

[ 1 ] [ 1, 2 ] [ 1, 3 ] [ 1, 4 ] [ 1, 5 ] [ 1, 6 ] [ 1, 7 ] [ 1, 8 ] [ 1, 9 ] 
[ 2 ] [ 2, 3 ] [ 2, 4 ] [ 2, 5 ] [ 2, 6 ] [ 2, 7 ] [ 2, 8 ] [ 2, 9 ] 
[ 3 ] [ 3, 4 ] [ 3, 5 ] [ 3, 6 ] [ 3, 7 ] [ 3, 8 ] [ 3, 9 ] 
[ 4 ] [ 4, 5 ] [ 4, 6 ] [ 4, 7 ] [ 4, 8 ] [ 4, 9 ] 
[ 5 ] [ 5, 6 ] [ 5, 7 ] [ 5, 8 ] [ 5, 9 ] 
[ 6 ] [ 6, 7 ] [ 6, 8 ] [ 6, 9 ] 
[ 7 ] [ 7, 8 ] [ 7, 9 ] 
[ 8 ] [ 8, 9 ] 
[ 9 ]
/**
 * @param {number} k
 * @param {number} n
 * @return {number[][]}
 */
var combinationSum3 = function(k, n) {
    let res = [];
    const dfs = (start,temp,sum) => {
        if(temp.length === k) {
            if(sum === n) {
                res.push(temp.slice());
            }
            return;
        }
        for(let i = start; i <= 9; i++){
            temp.push(i);
            console.info(temp);
            // sum这里未直接改变sum本身的值,所以在dfs之后也不需要做-i操作
            dfs(i + 1,temp,sum + i);
            temp.pop();
        }
    }
    // 1为值,非下标
    dfs(1,[],0);
    return res;
};

77. 组合

范围为[1, n],固定每个组合长度为k

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

输入: n = 4, k = 2
输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
[ 1 ] 
[ 1, 2 ] 
[ 1, 3 ] 
[ 1, 4 ] 
[ 2 ] 
[ 2, 3 ] 
[ 2, 4 ] 
[ 3 ] 
[ 3, 4 ] 
[ 4 ]
/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
    let res = [];
    const dfs = (start, temp) => {
        if(temp.length === k) {
            res.push(temp.slice());
            return;
        }
        for(let i = start; i <= n; i++) {
            temp.push(i);
            console.info(temp);
            dfs(i + 1, temp);
            temp.pop();
        }
    }
    // 注意这里1是值本身,不是下标
    dfs(1, []);
    return res;
};

78. 子集 不含重复数字

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

输入: nums = [1,2,3]
输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
[]未打印,[]会在最开始加入res结果集中

[ 1 ] 
[ 1, 2 ] 
[ 1, 2, 3 ] 
[ 1, 3 ] 
[ 2 ] 
[ 2, 3 ] 
[ 3 ]
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {
    let res=[];
    const dfs = (start, temp) => {
        res.push(temp.slice());
        for(let i = start; i < nums.length; i++){
            temp.push(nums[i]);
            console.info(temp);
            dfs(i+1, temp);
            temp.pop();
        }
    };
    // 这里0是下标;[]也是子集,会在最开始加入res
    dfs(0,[]);
    return res;
};

90. 子集 II 包含重复数字

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

输入: nums = [1,2,2]
输出: [[],[1],[1,2],[1,2,2],[2],[2,2]]
不去除横向(同一层)重复的打印结果
[ 1 ] [ 1, 2 ] [ 1, 2, 2 ] [ 1, 2 ] [ 2 ] [ 2, 2 ] [ 2 ]
去除横向(同一层)重复的打印结果;纵向可以重复
[ 1 ] [ 1, 2 ] [ 1, 2, 2 ] [ 2 ] [ 2, 2 ]
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function(nums) {
    let res=[];
    nums.sort((a,b)=>a-b);
    const dfs = (start,temp) => {
        res.push(temp.slice());
        for(let i = start; i < nums.length; i++){
            // 去掉横向同层重复。i > start更好理解,for是横向的
            if(i > start && nums[i-1]===nums[i]) continue;
            // if(i-1 >= start && nums[i-1]===nums[i]) continue;
            temp.push(nums[i]);
            dfs(i+1,temp);
            temp.pop();
        }
    }
    dfs(0,[]);
    return res;
};

131. 分割回文串

输入: s = "aab"
输出: [["a","a","b"],["aa","b"]]
/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function(s) {
    const isTrue = (str) => {
        return str === str.split('').reverse().join('');
    }
    let res = [];
    const dfs = (start, temp) => {
        if(start === s.length){
            res.push(temp.slice());
            return;
        }
        for(let i = start; i < s.length; i++) {
            if(isTrue(s.substring(start, i + 1))) {
                temp.push(s.substring(start, i + 1));
                dfs(i+1, temp);
                temp.pop();
            }
        }
    }
    dfs(0, []);
    return res;
};