• 回溯算法 | 子集问题(递增子序列) | 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    回溯算法——剪枝优化

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    deepstream6.2部署yolov5详细教程与代码解读
    RHEL 6入门之使用 ntsysv 、chkconfig 管理服务
    JAVA启动参数备完
    Linux常用命令操作
    Spring Boot 开启https访问(配置SSL证书)
    C++ 虚函数
    安装搭建PHP域名授权验证系统、盗版可以追踪双重鉴权、在线加密功能二次开发部署
    springboot红色吕梁网站的设计与开发毕业设计源码150923
    实战:如何优雅的从 Skywalking 切换到 OpenTelemetry
    JDK8和JDK11使用Hotswap Agent在idea进行热部署
  • 原文地址:https://blog.csdn.net/qq_44250700/article/details/126324490