当前位置: 首页 > news >正文

【LeetCode 热题 100】46. 全排列——回溯

Problem: 46. 全排列

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N * N!)
    • 空间复杂度:O(N)

整体思路

这段代码旨在解决一个经典的组合数学问题:全排列 (Permutations)。给定一个不含重复数字的数组 nums,它需要找出其所有可能的全排列,并返回一个包含这些排列的列表。

该算法采用的核心方法是 回溯法(Backtracking),这是一种通过 深度优先搜索(DFS) 来系统地探索所有可能解的搜索策略。

算法的整体思路可以分解为以下步骤:

  1. 逐位构建排列

    • 算法将生成一个排列的过程看作是逐个“填空”的过程。它定义了一个 dfs(i, ...) 函数,其核心任务是确定排列中第 i 个位置应该填入哪个数字。
  2. 状态维护

    • 为了辅助构建过程,算法使用了两个关键的数据结构:
      • List<Integer> path:用于存储当前正在构建的排列。
      • boolean[] onPath:一个与原数组 nums 等长的布尔数组,用作“访问标记”。onPath[j] = true 表示 nums[j] 这个数字已经被放入了当前的 path 中,不能再被使用。
  3. 递归与回溯的核心逻辑

    • dfs 函数从第 0 位开始 (i=0)。在为第 i 位选择数字时,它会遍历整个原始数组 nums
    • 对于 nums 中的每一个数字 nums[j],它会检查该数字是否已被使用(if (!onPath[j]))。
    • 选择 (Choose):如果 nums[j] 未被使用,就做出选择:
      a. 将 nums[j] 放入当前排列的第 i 位:path.set(i, nums[j])
      b. 将 nums[j] 标记为已使用:onPath[j] = true
    • 探索 (Explore):做出选择后,递归地调用 dfs(i + 1, ...),去解决下一个子问题——填充第 i+1 位。
    • 撤销选择 (Unchoose / Backtrack):当对 i+1 位的探索(即 dfs(i + 1, ...) 调用)返回后,必须撤销刚才的选择。这是回溯法的精髓。
      a. 将 nums[j] 标记为未使用:onPath[j] = false
      b. 这样,在下一次循环中,nums[j] 就可以被用来填充其他位置,从而形成不同的排列。
  4. 递归终止条件

    • i 的值等于数组长度 n 时(if (i == n)),意味着排列的所有 n 个位置都已经被成功填满。
    • 此时,一个完整的排列就构建好了(存储在 path 中)。
    • path 的一个深拷贝new ArrayList<>(path))添加到最终的结果列表 ans 中。必须使用拷贝,因为 path 本身会在回溯过程中被不断修改。

通过这个“选择-探索-撤销”的循环,算法能够不重不漏地遍历所有可能的排列组合。

完整代码

class Solution {/*** 计算给定数组的全排列。* @param nums 不含重复数字的整数数组* @return 包含所有全排列的列表*/public List<List<Integer>> permute(int[] nums) {int n = nums.length;// ans: 最终的结果列表List<List<Integer>> ans = new ArrayList<>();// path: 用于存储当前正在构建的单个排列路径// Arrays.asList(new Integer[n]) 创建一个固定大小的、由null填充的List,便于后续使用 set 方法List<Integer> path = Arrays.asList(new Integer[n]);// onPath: 标记数组,onPath[j]为true表示nums[j]已在当前path中boolean[] onPath = new boolean[n];// 从第 0 个位置开始进行深度优先搜索dfs(0, nums, ans, path, onPath);return ans;}/*** 深度优先搜索(回溯)辅助函数。* @param i 当前需要填充的排列位置的索引* @param nums 原始输入数组* @param ans 结果列表* @param path 当前构建的排列* @param onPath 标记数组*/private void dfs(int i, int[] nums, List<List<Integer>> ans, List<Integer> path, boolean[] onPath) {int n = nums.length;// 递归终止条件:当 i 等于 n 时,说明所有位置都已填满,一个完整的排列已生成if (i == n) {// 将当前路径的一个深拷贝加入到结果列表中// 必须是深拷贝,因为 path 会在回溯过程中被修改ans.add(new ArrayList<>(path));return; // 结束当前递归分支}// 遍历所有可用的数字,尝试将其放入第 i 个位置for (int j = 0; j < n; j++) {// 如果 nums[j] 还未在当前路径中使用过if (!onPath[j]) {// 选择:将 nums[j] 放入第 i 个位置path.set(i, nums[j]);// 标记 nums[j] 为已使用onPath[j] = true;// 探索:递归地去填充下一个位置 (i + 1)dfs(i + 1, nums, ans, path, onPath);// 撤销选择(回溯):将 nums[j] 标记为未使用,// 这样它就可以在其他分支中被用于其他位置。这是回溯法的核心。onPath[j] = false;}}}
}

时空复杂度

时间复杂度:O(N * N!)

  1. 排列数量:对于一个包含 N 个不同元素的集合,其全排列的数量是 N! (N的阶乘)。
  2. 构建每个排列的成本
    • 算法的搜索过程可以看作是在一棵“排列树”上进行DFS。这棵树有 N! 个叶子节点,每个叶子节点代表一个完整的排列。
    • 从根节点到任意一个叶子节点的路径长度为 N
    • 当到达一个叶子节点时(即 i == n),需要执行 new ArrayList<>(path) 操作,这是一个 O(N) 的操作,用于复制当前排列。
  3. 综合分析
    • 总的时间复杂度约等于 (叶子节点数量 * 到达叶子节点的成本) + (非叶子节点的计算成本)。
    • 非叶子节点的总数也与 N! 相关。
    • 一个更精确的视角是,总共有 N! 个排列,每个排列的生成和复制都需要 O(N) 的时间。因此,总时间复杂度为 O(N * N!)

空间复杂度:O(N)

  1. 主要存储开销:我们分析的是额外辅助空间,不包括存储最终结果的 ans 列表(否则空间将是 O(N * N!))。
    • List<Integer> path: 用于存储当前路径,其大小为 N。空间复杂度为 O(N)
    • boolean[] onPath: 标记数组,其大小为 N。空间复杂度为 O(N)
    • 递归调用栈dfs 函数的最大递归深度为 N(从 i=0i=n)。因此,递归栈所占用的空间为 O(N)

综合分析
算法所需的额外辅助空间由 pathonPath 和递归栈深度共同决定。它们都是 O(N) 级别的。因此,总的辅助空间复杂度为 O(N)

参考灵神

http://www.xdnf.cn/news/1168597.html

相关文章:

  • Windows 编程辅助技能:转到文档
  • Docker实战系列:使用Docker部署AI SSH客户端工具IntelliSSH
  • 2025年远程桌面软件深度评测:ToDesk、向日葵、TeamViewer全方位对比分析
  • Golang避免主协程退出方案
  • 期权分红怎么分的?
  • Thinkphp8使用Jwt生成与验证Token
  • Spring之【Bean工厂后置处理器】
  • MybatisPlus入门指南
  • LeetCode 658.找到K个最接近的元素
  • 豪鹏科技锚定 “AI + 固态” 赛道:从电池制造商到核心能源方案引领者的战略跃迁
  • leetcode 1695. 删除子数组的最大得分 中等
  • 浏览器解码顺序xss
  • 低成本、高泛化能力的无人机自主飞行!VLM-Nav:基于单目视觉与视觉语言模型的无地图无人机导航
  • excle中匹配加密手机号(同sheet中)
  • Springboot + MyBatis-Plus + PageHelper 分页性能混合优化方案
  • 解决栅格数据裁剪矢量数据问题两种方法,ArcGIS解决与PYTHON解决
  • 物联网_TDengine_EMQX_性能测试
  • 【Android】xml和Java两种方式实现发送邮件页面
  • API网关原理与使用场景详解
  • Apache Ignite 中 WHERE 子句中的子查询(Subqueries in WHERE Clause)的执行方式
  • Linux操作系统从入门到实战(十二)Linux操作系统第一个程序(进度条)
  • 北京养老金计算公式网页实现案例:从需求分析到架构设计
  • J2EE模式---前端控制器模式
  • Python 绘制各类折线图全指南:从基础到进阶
  • k8s:离线部署tomcatV11.0.9,报Cannot find /opt/bitnami/tomcat/bin/setclasspath.sh
  • zabbix“专家坐诊”第295期问答
  • 以太网基础⑥ ZYNQ PS端 基于LWIP的TCP例程测试
  • MATLAB软件使用频繁,企业如何做到“少买多用”?
  • MFC类Qt的自动布局框架
  • 力扣-链表相关题 持续更新中。。。。。。