Day25-回溯,leetcode491,46,47

  1. 非递减子序列
  • 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

  • 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

  • 思路

  • 一个元素不能重复使用,startIndex,调整下一层递归的起始位置。

/**
1. 递增性判断
  nums[i] < path[path.length - 1]:如果当前数字比上一个小,跳过,保证递增(等于也可以)。
2. 本层去重
  uset[nums[i] + 100]:本层递归中,遇到相同数字只选一次,防止重复子序列。
+100 是为了防止负数下标。
. 3递归与回溯
  backtracing(i + 1):每次递归从下一个元素开始,保证每个元素最多用一次。
  path.pop():回溯,撤销选择。
4. 收集结果
  只有当 path.length > 1 时才收集,保证子序列至少有两个元素。
 */
var findSubsequences = function(nums) {
    let result = []
    let path = []
    function backtracing(startIndex) {
        if(path.length > 1) {
            result.push(path.slice()) // 收集长度大于1的子序列
        }
        let uset = [] // 本层去重,防止同一层重复选相同数字
        for(let i = startIndex; i < nums.length; i++) {
             // 1. 保证递增(等于也可以)
            // 2. 本层不能选重复数字
            if((path.length > 0 && nums[i] < path[path.length - 1]) || uset[nums[i] + 100]) {
                continue
            }
            // uset[nums[i] + 100] 这样写的原因是:为了防止数组下标为负数。
            uset[nums[i] + 100] = true // 标记本层已选
            path.push(nums[i])
            backtracing(i + 1) // 递归,i+1保证每个元素最多用一次
            path.pop() // 回溯
        }
    }
    backtracing(0)
    return result
};

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

  • 思路

  • 排列问题需要一个used数组,标记已经选择的元素

/**
res:存放所有排列结果的二维数组。
path:当前递归路径,表示一个排列。
used:布尔数组,标记每个元素是否已被选用,防止重复选取。
backtracking(n, k, used):
    n 是原数组,k 是排列长度(等于 nums.length),used 是标记数组。
    当 path.length === k 时,说明收集到一个完整排列,加入结果。
    for 循环遍历所有元素,未被使用的元素才可加入 path。
    递归后回溯,撤销选择和标记

假设 nums = [1, 2, 3],在递归过程中,used 可能的状态如下:
    初始:used = [false, false, false](都没用过)
    选了第一个元素 1:used = [true, false, false]
    选了 1 和 2:used = [true, true, false]
    回溯撤销 2,再选 3:used = [true, false, true]
    全部回溯后又选 2:used = [false, true, false]
    依此类推...
每次递归时,used[i] 为 true 表示 nums[i] 已经在当前排列中,不能再选。
回溯时要把 used[i] 恢复为 false,以便后续递归可以重新选择。
 */
var permute = function(nums) {
    const res = [], path = [];
    backtracking(nums, nums.length, []);
    return res;
    
    function backtracking(n, k, used) {
        if(path.length === k) {
            res.push(Array.from(path)); // 收集一个完整排列
            return;
        }
        for (let i = 0; i < k; i++ ) {
            // used是一个和nums长度相同的布尔数组(数组元素全是true或false),用来标记每个数字是否已经被选过。
            if(used[i]) continue; // 跳过已使用的元素
            path.push(n[i]);
            used[i] = true; // 同支 // 标记已使用
            backtracking(n, k, used);// 递归
            path.pop();// 回溯,撤销选择
            used[i] = false;// 撤销标记
        }
    }
};

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

  • 思路

  • 数层去重

  • 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。

/**
1. 排序
  先对 nums 排序,使得相同元素相邻,便于后续去重。
2. used 数组
  used 是一个布尔数组,标记每个元素是否已被选用,防止重复选取。
3. 树层去重
    if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) continue
    只有当前一个相同元素在同一树层未被使用时,才跳过,避免生成重复排列。
4. 回溯过程
    每次递归都尝试选择未被使用的元素,递归后撤销选择和标记。
5. 收集结果
    当 path.length === nums.length 时,收集一个完整排列。
 */
var permuteUnique = function (nums) {
    // 先排序,方便去重
    nums.sort((a, b) => {
        return a - b
    })
    let result = []
    let path = []

    function backtracing( used) {
        if (path.length === nums.length) {
            result.push([...path]) // 收集一个完整排列
            return
        }
        for (let i = 0; i < nums.length; i++) {
            // 树层去重:同一层遇到重复元素只选第一个
            if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {
                continue
            }
            if (!used[i]) {
                used[i] = true
                path.push(nums[i])
                backtracing(used)
                path.pop()
                used[i] = false
            }


        }
    }
    backtracing([])
    return result
};



参考&感谢各路大神

posted @ 2025-06-21 07:19  安静的嘶吼  阅读(6)  评论(0)    收藏  举报