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) 的开销。
相关推荐
j_xxx404_5 分钟前
力扣困难算法精解:串联所有单词的子串与最小覆盖子串
java·开发语言·c++·算法·leetcode·哈希算法
big_rabbit050221 分钟前
[算法][力扣167]Two Sum II
算法·leetcode·职场和发展
Eward-an1 小时前
LeetCode 76. 最小覆盖子串(详细技术解析)
python·算法·leetcode·职场和发展
不想看见4042 小时前
Reverse Bits位运算基础问题--力扣101算法题解笔记
笔记·算法·leetcode
逆境不可逃2 小时前
LeetCode 热题 100 之 394. 字符串解码 739. 每日温度 84. 柱状图中的最大矩形
算法·leetcode·职场和发展
重生之后端学习2 小时前
62. 不同路径
开发语言·数据结构·算法·leetcode·职场和发展·深度优先
big_rabbit05022 小时前
[算法][力扣283]Move Zeros
算法·leetcode·职场和发展
重生之后端学习3 小时前
64. 最小路径和
数据结构·算法·leetcode·排序算法·深度优先·图论
We་ct3 小时前
LeetCode 212. 单词搜索 II:Trie+DFS 高效解法
开发语言·算法·leetcode·typescript·深度优先·图搜索算法·图搜索
样例过了就是过了3 小时前
LeetCode热题100 路径总和 III
数据结构·c++·算法·leetcode·链表