从零讲透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: List[int]

:rtype: List[List[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主等多位大佬的细心讲解,以上是我的一些笔记。

相关推荐
AI科技星4 分钟前
宇宙的几何诗篇:当空间本身成为运动的主角
数据结构·人工智能·经验分享·算法·计算机视觉
新手村领路人13 分钟前
python打包成exe
python·打包
胡桃不是夹子14 分钟前
torch和torchvision对应版本匹配官网下载
人工智能·python·深度学习
前端小L16 分钟前
二分查找专题(二):lower_bound 的首秀——精解「搜索插入位置」
数据结构·算法
一抓掉一大把32 分钟前
秒杀-StackExchangeRedisHelper连接单例
java·开发语言·jvm
星释1 小时前
Rust 练习册 :Minesweeper与二维数组处理
开发语言·后端·rust
老黄编程1 小时前
三维空间圆柱方程
算法·几何
Q_Q19632884751 小时前
python+django/flask基于深度学习的个性化携程美食数据推荐系统
spring boot·python·深度学习·django·flask·node.js·php
胡耀超1 小时前
通往AGI的模块化路径:一个可能的技术架构(同时解答微调与RAG之争)
人工智能·python·ai·架构·大模型·微调·agi
清空mega1 小时前
从零开始搭建 flask 博客实验(常见疑问)
后端·python·flask