从零讲透DFS-深度优先搜索-2(排序与组合)

一、核心知识点清单

1. 排列组合问题分类
  • 排列问题(顺序相关):如全排列(LeetCode 46)。

  • 组合问题(顺序无关):如子集(LeetCode 78)。

  • 关键区别:是否考虑元素的顺序。

2. 回溯模板强化
python 复制代码
def backtrack(path, choices):
    if 满足终止条件:
        记录结果
        return
    for 选择 in choices:
        if 选择不合法:  # 剪枝
            continue
        path.append(选择)          # 做选择
        backtrack(path, 新choices) # 递归
        path.pop()                # 撤销选择(回溯)
3. 去重技巧
  • 排序+跳过重复元素:适用于输入含重复数字的情况(如LeetCode 47)。

  • 示例代码:

    python 复制代码
    nums.sort()  # 先排序
    for i in range(len(nums)):
        if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
            continue  # 跳过重复分支
4. 剪枝优化场景
  • 提前终止:当路径不可能满足条件时(如组合总和超过目标值)。

  • 跳过无效分支:如全排列中已使用的数字不再选择。

5. 视频教程:

6. 图文教程:


二、学习安排(2小时)

第1个30分钟:理解排列与组合的区别

对比练习:

  • 全排列(1,2,3 → 6种顺序不同的解)。

    \[1,2,3\],\[1,3,2\],\[2,1,3\],\[2,3,1\],\[3,1,2\],\[3,2,1\]

  • 子集(1,2,3 → 8种包含不同元素的子集)。

\[空集\],\[1\],\[2\],\[3\],\[1,2\],\[1,3\],\[2,3\],\[1,2,3\]

显然**1,2 2,1是两个排列,但是为同一个组合。**

画决策树:

  • 用纸笔画出输入为1,2时的全排列和子集决策树,体会回溯过程。
第2个30分钟:全排列代码实现
  1. 题目:LeetCode 46. 全排列(无重复数字)。

    • 代码框架:

      python 复制代码
      def permute(nums):
          def backtrack(path):
              if len(path) == len(nums):
                  res.append(path.copy())
                  return
              for num in nums:
                  if num in path:  # 剪枝:已使用的数字跳过
                      continue
                  path.append(num)
                  backtrack(path)
                  path.pop()
          res = []
          backtrack([])
          return res
    • 关键点 :用path记录已选数字,通过if num in path剪枝。

值得注意的是上面的代码在力扣的运行中频繁报错AttributeError: 'list' object has no attribute 'copy', 这不禁联想到相同情况之前引用split报错,这里的原因是访问类型的不匹配,就好比split中我们试图将字符串分隔,但可能我们要操作的对象并不是字符串类型,而是列表类型。后面我将这句copy改成append("".join(path))也是报错。这里代码尝试把整数列表path连接成字符串。不过join方法要求传入的可迭代对象元素是字符串,而path中的元素是整数,所以会引发 TypeError。

所以可以得出这一块我们正在操作的是列表类型下的整数元素,最终将代码改成append(path:),即为正确答案。

这里不断修改类型是因为参考的是力扣的题目,有输入输出类型的限制。

:type nums: Listint

:rtype: ListList\[int]

第3个30分钟:子集问题与去重
  1. 题目:LeetCode 78. 子集。

    • 代码框架:

      python 复制代码
      def subsets(nums):
          def backtrack(start, path):
              res.append(path[:])  # 所有节点均记录结果
              for i in range(start, len(nums)):  # 避免重复选择
                  path.append(nums[i])
                  backtrack(i + 1, path)  # 从i+1开始选
                  path.pop()
          res = []
          backtrack(0, [])
          return res
    • 与全排列的区别 :通过start参数控制选择范围,避免顺序不同但元素相同的重复解。

第4个30分钟:全代码去重问题
  1. 题目:LeetCode 47. 全排列2。

    • 剪枝技巧:标记已使用过的数组、相同数字按顺序使用避免重复。

    • 代码片段:

      python 复制代码
      class Solution(object):
          def permuteUnique(self, nums):
              """
              :type nums: List[int]
              :rtype: List[List[int]]
              """
              def backtrack(path, used):
                  if len(path) == len(nums):
                      res.append(path[:])
                      return
                  for i in range(len(nums)):
                  # 剪枝条件:
                  # 1. 当前数字已使用过
                  # 2. 当前数字与前一个数字相同,且前一个数字未被使用(保证相同数字的顺序)
                      if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
                          continue
                      used[i] = True
                      path.append(nums[i])
                      backtrack(path, used)
                      path.pop()
                      used[i] = False
      
              nums.sort()  # 必须先排序,使相同数字相邻
              res = []
              used = [False] * len(nums)
              backtrack([], used)
              return res

i > 0 and nums[i] == nums[i-1] and not used[i-1]:当前数字与前一个数字相同且前一个数字未被使用(保证相同数字按顺序使用,避免重复)

全排序1中代码直接检查num in path来判断是否使用过,这在有重复数字时会失效。


三、常见错误与调试技巧

  1. 忘记回溯:

    • 错误示例 :在path.append(num)后忘记path.pop(),导致结果重复。

    • 调试方法 :在递归前后打印path,观察状态变化。

  2. 去重逻辑错误:

    • 案例:LeetCode 47(含重复数字的全排列)中未排序直接跳过重复数。

    • 修正 :必须先排序,再判断nums[i] == nums[i-1]。

文章感谢各个CSDN博主、b站up主等多位大佬的细心讲解,以上是我的一些笔记。

相关推荐
用户老李9 分钟前
卡尔曼滤波自学教程 10:无迹卡尔曼滤波(UKF)
算法
天外飞雨道沧桑28 分钟前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
hhhjhl33 分钟前
Yup 跨端三方库仓颉适配全流程
java·开发语言
运维行者_44 分钟前
网络监控工具怎么选?从设备监控到智能运维的能力清单
运维·服务器·开发语言·网络·人工智能·支持向量机·php
KeepRobots1 小时前
11-01 温哥华不回拨:用 60 行 Python 查你的工单系统会不会差一小时(KeepRobots 工程笔记)
python
xiao77mwq1 小时前
你以为 OCR 慢?实测 81.3 s 里全是故障代价:如何把「定格态」消掉,让耗时从 6.5 s 到 13 倍差距显出原形
python
深圳市益普科技有限公司1 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法
“AI国潮设计-小江”2 小时前
【AIGC实战】Python+SDXL打造“潮汕英歌舞IP”万能立牌插件,批量生成“发财元宝蛋糕”视觉营销方案(附源码)
开发语言·人工智能·python·prompt·aigc
lhbweilai2 小时前
分析FreeRTOS内核对象API的源码实现(八)
c语言·开发语言·stm32·单片机
努力努力再努力wz2 小时前
【C++高阶系列】C++20 协程底层原理:函数为什么能暂停?从状态机到 Task,彻底串起挂起、恢复与协程调度
开发语言·c++·c++20·1024程序员节