回溯算法篇--递增子序列

417 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

1.题目一

491. 递增子序列

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

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

 

示例 1:

输入:nums = [4,6,7,7]

输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

示例 2:

输入:nums = [4,4,3,2,1]

输出:[[4,4]]  

提示:

1 <= nums.length <= 15

-100 <= nums[i] <= 100


思路

回溯法三部曲

  • 递归函数的返回值以及参数
  • 回溯函数终止条件
  • 单层搜索的过程 还是那句话画图能更清晰了解,此处我就不画图了。

1.子集的话没有明确的递归终止条件,只要是深搜过程中不重复的子集都可以加入res中 2.当for循环执行完成之后会自动开始回溯执行path.pop(),这也是不需要明确的递归终止条件的原因,会自动开始回溯

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

递增子序列考虑的情况更多,这也是需要注意的点,use 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

当前递归结束后,要将当前的选择撤销,回到选择前的状态,去考察另一个选择,即进入下一轮迭代,尝试另一种切分的可能。

这样才能在解的空间树中,把路走全了,搜出所有的合法部分解。


代码

var findSubsequences = function(nums) {
    let res = [];
    let path = [];
    const dfs = (startIndex, path) => {
        let used = []
        if(path.length >= 2) {
            res.push(path.slice())
        }
        for(let i = startIndex; i < nums.length; i++) {
            if((path.length > 0 && nums[i] < path[path.length - 1]) || used[nums[i]]) {
                continue
            }
            used[nums[i]] = true
            path.push(nums[i])
            dfs(i+1, path)
            path.pop()
        }
    }
    dfs(0, []);
    return res;
};