回溯:491. 递增子序列、46. 全排列、47. 全排列 II

提示:努力生活,开心、快乐的一天


491. 递增子序列

题目链接:491. 递增子序列

💡解题思路

  1. 有序的递增子序列:所以不能对原数组进行排序,原来的去重不可使用,依旧是树层去重,树枝不去重;递增子序列中 至少有两个元素:所以取所有节点后,需要加一层判断在这里插入图片描述
  2. 回溯三部曲
  • 递归函数参数:startIndex,调整下一层递归的起始位置
  • 终止条件:可以不加终止条件,startIndex每次都会加1,并不会无限递归,但本题收集结果有所不同,题目要求递增子序列大小至少为2
  • 单层搜索逻辑:同一父节点下的同层上使用过的元素就不能再使用了所以,定义一个数组,对本层元素进行去重,还有一层判断,就是递增子序列,所以当前的nums[i]<path[path.length - 1]

🤔遇到的问题

  1. 终止条件:收集结果的时候,不能加return,因为要取树上的所有节点
  2. 因为原数组不是有序的,所以,不能使用原来的去重方式

💻代码实现

回溯

var findSubsequences = function (nums) {
    let result = []
    let path = []
    const backtracking = (startIndex) => {
        //递增子序列大小至少为2
        if (path.length > 1) {
            result.push([...path])
            // 注意这里不要加return,因为要取树上的所有节点
        }
        //可以不加终止条件,startIndex每次都会加1,并不会无限递归
        if (startIndex >= nums.length) return
        //该数组对本层元素进行去重
        let uset = []
        for (let i = startIndex; i < nums.length; i++) {
            //这次排序的去重,但此题是不能进行提前排序的,否则,所有的子序列就都是递增子序列了
            // if (i > startIndex && nums[i] === nums[i - 1]) continue
            //出现过的数字,放在数组的该位置上,+100的原因是,题目要求-100 <= nums[i] <= 100
            if ((path.length > 0 && nums[i] < path[path.length - 1]) || uset[nums[i]+100]) continue
            //出现过的数字,该位置置为true
            uset[nums[i] + 100] = true
            path.push(nums[i])
            backtracking(i + 1)
            path.pop()
            //不需要对uset做回溯,因为uset定义在递归中,每次循环都是刷新,只记录该层的数据
        }
    }
    backtracking(0)
    return result
};

🎯题目总结

1.不能一直套用模版啦,因为此题不能排序,所以去重的方式需要进行替换,另外增加了别的判断条件


46. 全排列

题目链接:46. 全排列

💡解题思路

  1. 排列是有序的在这里插入图片描述
  2. 回溯三部曲
  • 递归函数参数:定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果,因为是排列,有序的,每次从头取值,不需要startIndex,但需要一个数组used,标记已经选择的元素
  • 递归终止条件:叶子节点,就是收割结果的地方。当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点
  • 单层搜索的逻辑:排列问题,每次都要从头开始搜索;used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

🤔遇到的问题

  1. used数组也需要做回溯

💻代码实现

回溯

var permute = function (nums) {
    let result = []
    let path = []
    //used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次
    const backtracking = (uesed) => {
        //当收集元素的数组path的大小达到和nums数组一样大的时候,
        //说明找到了一个全排列,也表示到达了叶子节点
        if (path.length === nums.length) {
            result.push([...path])
            return
        }
        //排列问题,每次都要从头开始搜索
        for (let i = 0; i < nums.length; i++) {
            if (uesed[i]) continue // path里已经收录的元素,直接跳过
            uesed[i] = true
            path.push(nums[i])
            backtracking(uesed)
            path.pop()
            uesed[i] = false
        }
    }
    backtracking([])
    return result
};

🎯题目总结

排列的特点:

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

47. 全排列 II

题目链接:47. 全排列 II

💡解题思路

  1. 这道题目和46.全排列的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了
  2. 树层去重
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
    continue;
}

在这里插入图片描述

🤔遇到的问题

  1. 去重,必须先去重

💻代码实现

回溯法

var permuteUnique = function (nums) {
    let result = []
    let path = []
    nums.sort((a, b) => a - b)//去重必须先排序
    const backtracking = (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]) continue
            used[i] = true
            path.push(nums[i])
            backtracking(used)
            path.pop()
            used[i] = false
        }
    }
    backtracking([])
    return result
};

🎯题目总结

  1. 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
  2. 对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
    树层上去重(used[i - 1] == false)
    在这里插入图片描述
    树枝上去重(used[i - 1] == true)
    在这里插入图片描述
    既然 used[i - 1] == false也行而used[i - 1] == true也行,那为什么还要写这个条件呢?NoNo
    一定要加上 used[i - 1] == false或者used[i - 1] == true,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false。 所以这个条件要写上

🎈今日心得

排列问题,初步接触,还是有自己没考虑到的点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值