491 递增子序列(dfs)

1. 问题描述:

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

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

说明:

给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/increasing-subsequences

2. 思路分析:

① 分析题目可以知道我们需要找到所有递增的子序列,所以一开始我们是不知道存在多少个的,只有搜索完所有的递增的子序列才知道最终的答案,所以一开始比较容易想到的是使用dfs来搜索所有的子序列,对于这道题目来说dfs搜索有两个思路:

(1) 第一个思路递归的含义是以当前位置i开始递增的所有子序列,例如例子[4, 6, 7, 7],我们可以尝试以4,6,7,7开头的递增的子序列,当往下递归的时候我们需要判断出下一个数字数字是否大于了当前递归过程中形成的递增子序列的上一个数字,假如条件成立才往下进行递归,基于这个思路我们可以知道需要在for循环中进行递归这样才可以尝试以当前数字开始的所有递增子序列,并且for循环递归的范围为[pos, len(nums)]:我们往下递归的时候肯定是当前位置的下一个位置

(2) 第二个比较容易想到的思路是:对于当前位置的nums[i],我可以拿取当前的数字也可以不拿取当前的数字,这相当于是一棵二叉树,我们画出其中的递归树就可以很好理解了,所以这里是存在两种平行状态的,我们可以根据这两个平行状态递归下去

② 上面是基本的思路,我们还有一个很重要的问题还没有解决:去重,从例子[4, 6, 7, 7]中可以发现我们假如只是简单判断一下当前可能递归的数字是否大于递增子序列的上一个数字那么会造成重复的答案,比如之前形成了[6,7]的答案,在递归另一个平行状态的数字7的时候假如判断7 > 6往下递归得到的答案就与之前的[6,7]答案重复了,所以去重是一个很关键的问题

③ 对于第一种在for循环中进行递归的思路可以使用set集合进行去重,这里可以使用到的一个技巧是:在每一次递归前的时候都创建一个set集合,将往下递归的数字加入到set集合中这样当递归平行状态的时候假如遇到相同的数字就不会再递归下去了,比如数字[1,2,3,4,1,1,1,1],第一个1往下递归到第五个1的时候这个时候set集合是重新创建出来的,所以在递归的过程中会首先形成[1,1],[1,1,1],[1,1,1,1]这样的序列,但是后面退回到第一层的平行状态的时候由于set集合添加了第一个1,所以尝试第一层的第五个1的平行状态的时候发现set集合中已经存在了这样的数字了,所以continue跳过了,不会再递归下去了,所以不会再递归后面第五个1开始的递增排列[...1,1,1,1]

④ 对于第二种两个平行状态的递归可以使用一个辅助变量来记录是否存在重复的答案,在力扣中一位大佬的去重写得非常棒,其中去重的技巧是非常值得学习的:

https://leetcode-cn.com/problems/increasing-subsequences/solution/di-gui-gao-ding-liu-xing-dai-ma-pan-zhong-by-time-/

往下递归的时候判断出[last + 1,pos)的区间是否存在相同的数字如果不存在而且满足递增子序列的条件那么往下递归下去,拿去当前的数字的时候那么下一次递归检查的范围应该是[pos, pos + 1],当不拿取当前数字的时候那么检查的范围应该是[last + 1, pos]

⑤ 可以对照着力扣中的90 子集的那一道题目进行理解,90题也是涉及到去重(排序 + 去重)的问题,里面的一个去重技巧时候结合了排序来处理的:排序之后判断相邻的元素是否存在重复

 

3. 代码如下:

for循环中递归:

from typing import List


class Solution:
    def dfs(self, nums: List[int], rec: List[int], res: List[List[int]], pos: int):
        if len(rec) >= 2: res.append(rec[:])
        t = set()
        for i in range(pos, len(nums)):
            # set函数去重
            if nums[i] in t: continue
            if not rec or nums[i] >= rec[-1]:
                t.add(nums[i])
                # 这里使用集合相加的方式所以可以不用进行列表弹出元素的回溯的操作
                self.dfs(nums, rec + [nums[i]], res, i + 1)

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = list()
        self.dfs(nums, list(), res, 0)
        return res

两个平行状态递归:

from typing import List


class Solution:
    # 去重
    def check(self, nums: List[int], pre: int, pos: int):
        for i in range(pre + 1, pos):
            if nums[i] == nums[pos]: return False
        return True

    # 两个平行状态: 拿与不拿
    def dfs(self, nums: List[int], rec: List[int], res: List[List[int]], pre: int, pos: int):
        if pos == len(nums): return
        if (not rec or nums[pos] >= rec[-1]) and self.check(nums, pre, pos):
            rec.append(nums[pos])
            if len(rec) >= 2: res.append(rec[:])
            self.dfs(nums, rec, res, pos, pos + 1)
            rec.pop()
        self.dfs(nums, rec, res, pre, pos + 1)

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = list()
        self.dfs(nums, list(), res, -1, 0)
        return res

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值