leetcode hot100 46. 全排列 medium 递归回溯 dfs


回溯 + 状态标记

回溯的核心在于:尝试选择 -> 递归进入下一层 -> 撤销选择(回退)。

  • 路径 (path):记录当前已经选了哪些数字。
  • 选择列表:nums 中除去已经在 path 里的数字。
  • 结束条件:path 的长度等于 nums 的长度。
  • 状态重置:为了让同层级的其他分支能重新使用当前数字,必须在递归返回后将其从 path 中移除。

DFS

eg:nums = [1,2,3]

步骤 操作 当前 path 当前 used 动作说明
1 dfs() (L1) [] [F, F, F] 初始状态
2 append(1) [1] [T, F, F] 选了 1,去下一层找老二
3 append(2) [1, 2] [T, T, F] 选了 2,去下一层找老三
4 append(3) [1, 2, 3] [T, T, T] 选了 3,满了!
5 SAVE [1, 2, 3] [T, T, T] 存入 res
6 pop() [1, 2] [T, T, F] 回溯:撤销 3 的选择
7 pop() [1] [T, F, F] 回溯:撤销 2,接下来尝试选 3 做老二
8 append(3) [1, 3] [T, F, T] 选了 3 做老二
9 ... ... ... 重复上述逻辑...
python 复制代码
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        n =  len(nums)
        res = []
        path = []   # 记录当前已经选了哪些数字   
        used = [False] * n  # 布尔数组记录数字是否已被使用

        def dfs(): # 不需要传 i
            # path达到nums长度,停止
            if len(path) == n:
                res.append(path[:])
                return

            # 检查nums里还有哪些能填进path的
            for idx, use in enumerate(used):
                if not use:
                    path.append(nums[idx]) # 添加nums[idx]
                    used[idx] = True       # 标记用过
                    dfs()                  # 递归,继续下一个坑位

                    # path达到nums长度,递归结束
                    path.pop()             # 弹出加入的nums[idx](回溯的核心)
                    used[idx] = False      # 重置 nums[idx]为没用过


        dfs()  
        return res

为什么用 path[:]?

在 Python 中,path 是一个列表对象(引用)。如果不使用切片 [:] 拷贝一份副本,最终 res 里的所有元素都会指向同一个被清空的 path 对象。

时间复杂度: O ( n × n ! ) O(n \times n!) O(n×n!)

全排列共有 n ! n! n! 个,每个排列需要 O ( n ) O(n) O(n) 的时间复制到结果集中。
空间复杂度: O ( n ) O(n) O(n)

递归深度为 n n n,且需要 used 数组和 path 数组。


切片

选了 nums[i] 以后,其它所有元素都还能选(除了它自己)

python 复制代码
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        def backtrack(nums, tmp):  #(待选列表,已有排列)
        	 #待选列表为空(所有数字有选完了)
            if not nums:
                res.append(tmp)  # 当前排列加入res
                return 
            # 依次选择待选列表的元素,进入排列。
            for i in range(len(nums)):
                backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]]) #注意是[nums[i]], 不是nums[i]
        backtrack(nums, [])
        return res

值传递:这段代码在每次递归时,都通过 + 号产生了全新的列表对象传给下一层。

自动恢复:由于每一层使用的 nums 和 tmp 都是上一层传过来的"快照",当前层对它们的任何操作都不会影响到上一层的变量。当递归函数返回时,上一层的变量依然是原来的样子。

维度 标准回溯 切片传递
空间效率 更高 。全局共用一个 pathused,空间复杂度 O ( n ) O(n) O(n)。 较低。每层都在创建新的列表切片,会产生大量临时对象。
可读性 逻辑略复杂,需要处理"撤销"动作。 非常直观,更像纯粹的递归思维。
性能 。数组原地修改速度最快。 。列表拼接和切片操作在 Python 里是 O ( k ) O(k) O(k) 的开销。
相关推荐
逆境不可逃2 小时前
LeetCode 热题 100 之 76.最小覆盖子串
java·算法·leetcode·职场和发展·滑动窗口
踩坑记录2 小时前
leetcode hot100 78. 子集 递归回溯 medium 位运算法
leetcode
Frostnova丶2 小时前
LeetCode 761. 特殊的二进制字符串
算法·leetcode
再难也得平3 小时前
[LeetCode刷题]49.字母异位词分组(通俗易懂的java题解)
java·开发语言·leetcode
近津薪荼3 小时前
dfs专题9——找出所有子集的异或总和再求和
算法·深度优先
52Hz1183 小时前
力扣131.分割回文串、35.搜索插入位置、74.搜索二维矩阵、34.在排序数组中查找...
python·算法·leetcode
Tisfy3 小时前
LeetCode 761.特殊的二进制字符串:分治(左右括号对移动)
算法·leetcode·字符串·递归·分治
再难也得平4 小时前
[LeetCode刷题]1.两数之和(java题解)
java·算法·leetcode
踩坑记录4 小时前
leetcode hot100 39. 组合总和 medium 递归回溯
leetcode