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 数组。


切片

选了 numsi 以后,其它所有元素都还能选(除了它自己)

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) 的开销。
相关推荐
圣保罗的大教堂4 小时前
leetcode 3483. 不同三位偶数的数目 简单
leetcode
mmmmath_35 小时前
LeetCode.018.四数之和
数据结构·算法·leetcode
圣保罗的大教堂7 小时前
leetcode 836. 矩形重叠 简单
leetcode
土司大王7 小时前
LeetCode hot100——394.字符串解码:Java 双栈模拟
java·算法·leetcode
a187927218317 小时前
【算法】双指针与滑动窗口(三):相向双指针——比较、排除、收缩
算法·leetcode·双指针·滑动窗口·原理·相向双指针·算法讲解
6Hzlia8 小时前
【Classic 150 刷题计划】 LeetCode 14. 最长公共前缀 | C++ 纵向扫描法与防越界细节
c++·算法·leetcode
我不会起名字32221 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
彧azz1 天前
DFS与BFS:图遍历的两大核心算法
数据结构·学习·算法·深度优先·广度优先
Tisfy1 天前
LeetCode 0836.矩形重叠:xy两方向分别看
数学·leetcode·题解·模拟
青山木1 天前
Hot 100 --- 最长递增子序列
java·数据结构·算法·leetcode·动态规划