回溯题解——全排列【LeetCode】

发布于:2025-07-08 ⋅ 阅读:(15) ⋅ 点赞:(0)

46. 全排列

一、算法逻辑(逐步通顺讲解每一步思路)

该算法使用了典型的 回溯(backtracking)+ 状态数组 思路,逐层递归生成排列。

题目目标:给定一个无重复整数数组 nums,返回其所有可能的全排列

具体思路如下:


✅ 1️⃣ 初始化变量:

  • n = len(nums):数组长度,表示排列长度;

  • ans = []:最终结果列表,保存所有排列;

  • path = [0] * n:当前正在构造的排列路径(长度固定为 n);

  • on_path = [False] * n:记录哪些下标的元素已经被使用,用来避免重复选择。


✅ 2️⃣ 定义递归函数 dfs(i):表示当前正在构建第 i 位上的数字。

  • 递归终止条件:当 i == n,说明整个排列已经构造完成,path 中已有一个完整解;

    • 使用 path.copy() 将当前路径添加到 ans 中(防止引用修改)。


✅ 3️⃣ 遍历所有可选数字:

  • 遍历 nums 中每个元素(通过索引 j);

  • 只有当 on_path[j] == False,才说明当前数字 nums[j] 还没被选过;

  • nums[j] 放入第 i 位的 path[i] 中,并标记为已使用;

  • 然后递归下一层 dfs(i+1)

  • 回溯回来后,将 on_path[j] 设为 False,撤销选择,进入下一个分支。


✅ 4️⃣ 启动递归:

dfs(0) 开始,构造第 0 位,依次递归直到第 n-1 位,生成所有可能排列。


二、核心点总结

该算法的核心是:

使用回溯(DFS)方式按位构建排列,每一层递归尝试将所有未使用的数字填入当前位置,通过布尔数组标记状态,避免重复选择,实现全排列。

✅ 回溯模板典型写法(构造 + 撤销);
✅ 使用显式路径数组 path[i] 来记录当前位置数字;
✅ 使用布尔标记数组 on_path 控制元素是否被使用;
✅ 整体结构是“选择—递归—撤销选择”的排列构造范式。

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        ans = []
        path = [0] * n
        on_path = [False] * n
        def dfs(i:int) -> None:
            if i == n:
                ans.append(path.copy())
                return 
            for j, on in enumerate(on_path):
                if not on:
                    path[i] = nums[j]
                    on_path[j]=True
                    dfs(i+1)
                    on_path[j] = False
        dfs(0)
        return ans

三、时间复杂度分析

  • 一共有 n! 个不同的排列;

  • 每个排列的构造过程为 O(n),因为每一层选择都要扫描一遍 on_path

所以总时间复杂度为:

O(n × n!)


四、空间复杂度分析

  • 显式开销:

    • path 数组:O(n)

    • on_path 数组:O(n)

  • 隐式开销(递归栈):

    • 最多递归 n 层(深度为 n)

所以总空间复杂度为:

O(n)(不包含输出)

如果包含所有输出结果,则为:

O(n × n!)(因为结果中包含 n! 个长度为 n 的排列)


✅ 总结一句话

该算法采用典型回溯框架,按位构造路径、记录状态、递归搜索,最终在 O(n × n!) 时间与 O(n) 空间下枚举出所有排列,是解决排列问题最通用、稳定的模板方案。


网站公告

今日签到

点亮在社区的每一天
去签到