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) 的开销。
相关推荐
To_OC8 小时前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
想吃火锅10057 天前
【leetcode】121.买卖股票的最佳时机js/c++
算法·leetcode·职场和发展
凌波粒7 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
退休倒计时7 天前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
珊瑚里的鱼7 天前
【递归】汉诺塔
算法·深度优先
小欣加油7 天前
leetcode3612 用特殊操作处理字符串I
数据结构·c++·算法·leetcode·职场和发展
凌波粒7 天前
LeetCode--90.子集II(回溯算法)
数据结构·算法·leetcode
旖-旎7 天前
《LeetCode 417 太平洋大西洋水流问题 FloodFill DFS 解法》
c++·算法·深度优先·力扣·floodfill
凌波粒7 天前
LeetCode--46.全排列(回溯算法)
数据结构·算法·leetcode
吃着火锅x唱着歌7 天前
LeetCode 2530.执行K次操作后的最大分数
数据结构·算法·leetcode