回溯算法 | 子集问题(递增子序列) | leecode刷题笔记

跟随carl代码随想录刷题
语言:python


78. 子集

题目:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
👉示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
👉示例 2:
输入:nums = [0]
输出:[[],[0]]

题目分析

  • 组合问题分割问题都是收集树的叶子节点

  • 子集问题找树的所有节点

  • 本题终止条件可以不要,for循环结束后自动结束

    • if startIndex == len(nums):
    • return
  • 注意:res.append(path[:]) # 是path[:],而不是path

完整代码如下

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []

        def backtracking(nums, startIndex):

            if path not in res:
                res.append(path[:])  # 注意是path[:],而不是path

            # 单层递归逻辑
            for i in range(startIndex, len(nums)):
                path.append(nums[i])
                backtracking(nums, i+1)
                path.pop()
        
        backtracking(nums, 0)
        return res

90. 子集 II

题目:给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
👉示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
👉示例 2:
输入:nums = [0]
输出:[[],[0]]

题目分析

因为数组中元素存在重复,因此在单层逻辑编写的时候要进行树层去重判断:如果当前元素与前一个元素重复了,那就终止这层逻辑,即:终止对当前元素的操作。接着对下一个元素进行判断。
因此要注意两点:

  • 剪枝之前要提前排序.sort()
  • 树层去重:if i > startIndex and nums[i] == nums[i-1]: continue

注:[:]中,省略头部和尾部索引值,功能是复制列表

完整代码如下

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        nums.sort()  # 剪枝之前先排序

        def backtracking(nums, startIndex):
            if path not in res:
                res.append(path[:])  # 这里一定要写path[:],而不是path。

            # 终止条件
            if startIndex == len(nums):
                return

            # 单层递归逻辑
            for i in range(startIndex, len(nums)):
                # 树层去重
                if i < startIndex and nums[i]==nums[i-1]:
                    continue
                path.append(nums[i])
                backtracking(nums, i+1)
                path.pop()

        backtracking(nums, 0)
        return res

491. 递增子序列

题目:给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

题目分析

  • 递增子序列
    • 本题要求递增子序列,所以不能对数组排序,否则就破坏了原数组的顺序。

    • ⭐️本题有一个坑:只要求递增子序列,而不要求连续递增子序列

      • eg:如果nums = [4, 7, 6, 7],那么[4, 7, 7]是一个所求集合。
      • 😄因此判断当前元素是否符合进入递增序列的条件,要看它的值当前path中的最后一个元素值的大小关系:if path and nums[i] < path[-1]: continue
      • 😭错误写法:if nums[i] < nums[i-1]: continue # 如果当前元素比前一个元素小,那就结束对这个元素的操作。【这个比较是不对的,会导致直接在6就continue了,导致集合[4, 7, 7],没办法统计】
    • 剪枝优化操作:

      • 如果一个元素已经在前面的集合中作为头元素出现过了,那么对这个元素的操作就终止:usage_list = set()if nums[i] in usage_list: continue
      • eg:nums = [4, 4, 5],以索引为0的元素4为首部的集合有:[4, 4], [4, 4, 5], [4, 5]。因此在遍历到 索引为1的元素4 时,直接continue,因为此时以它为首部的集合[4, 5]已经出现过了,没必要再统计。
  • 至少有两个元素
    • 所以:if len(path) >= 2: res.append(path) # 只会添加长度大于等于2的集合
  • 因为一个元素不能重复使用,所以要使用startIndex作为每次回溯的起始点。
  • 没有剪枝优化时,直接在进入结果集时加一个条件判断就好了:if path not in res: res.append(path)

完整代码如下

回溯算法

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []

        def backtracking(nums, startIndex):
            if path not in res and len(path)>=2:
                res.append(path[:])
            
            # 单层回溯逻辑
            for i in range(startIndex, len(nums)):
                # 判断是否是递增的
                if path and nums[i] < path[-1]:
                    continue
                path.append(nums[i])  # 读取当前元素
                backtracking(nums, i+1)  # 回溯
                path.pop()

        backtracking(nums, 0)
        return res

回溯算法——剪枝优化

增加一个对集合头部元素是否出现过的判断

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []

        def backtracking(nums, startIndex):
            if path not in res and len(path)>=2:
                res.append(path[:])
            
            # 单层回溯逻辑
            usage_list = set()
            for i in range(startIndex, len(nums)):
                # 判断是否是递增的
                if path and nums[i] < path[-1] or nums[i] in usage_list:
                    continue
                usage_list.add(nums[i])
                path.append(nums[i])  # 读取当前元素
                backtracking(nums, i+1)  # 回溯
                path.pop()

        backtracking(nums, 0)
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值