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

力扣(全排列)

解析 LeetCode 46. 全排列:回溯算法的经典实践

在算法的奇妙世界中,LeetCode 46. 全排列问题是回溯算法的典型应用场景。该问题要求生成一个不含重复数字的数组的所有可能全排列,核心在于利用回溯思想遍历所有元素的排列组合。

一、题目分析

在这里插入图片描述

(一)问题定义

给定一个不含重复数字的整数数组 nums,返回其所有可能的全排列。例如,nums = [1,2,3] 时,输出应包含 [1,2,3][1,3,2][2,1,3][2,3,1][3,1,2][3,2,1] 共 6 种排列。

(二)核心挑战

  1. 穷举所有排列:需要确保不重复、不遗漏地生成所有可能的排列。
  2. 回溯的正确应用:在递归过程中,需要合理“选择”元素加入排列,递归返回后“撤销选择”(回溯 ),以尝试其他排列组合。
  3. 状态标记:需要标记元素是否已被使用,避免重复选择同一元素。

二、算法思想:回溯算法的深度遍历

(一)回溯算法的核心

回溯算法是一种**深度优先搜索(DFS)**的变体,其核心思想是:

  • 选择:在当前步骤,选择一个未使用的元素,加入当前排列。
  • 递归:递归处理下一个位置,继续选择元素。
  • 回溯:递归返回后,撤销当前选择(移除已加入的元素,标记为未使用 ),尝试其他选择。

这种“选 -> 递归 -> 回溯”的流程,能高效枚举所有排列组合,确保不重复、不遗漏。

(二)本题的回溯逻辑

  1. 状态标记:使用布尔数组 used 标记元素是否已被加入当前排列,避免重复选择。
  2. 递归终止条件:当当前排列的长度等于数组长度时,说明已生成一个完整排列,将其加入结果列表。
  3. 选择与回溯:遍历数组中的每个元素,若未被使用,则加入当前排列,标记为已使用,递归处理下一个位置;递归返回后,移除该元素,标记为未使用,继续遍历下一个元素。

三、代码实现与详细解析

class Solution {// 存储所有全排列结果List<List<Integer>> result = new ArrayList<>(); // 存储当前正在构建的排列List<Integer> answer = new ArrayList<>(); // 标记元素是否已被使用boolean[] used; public List<List<Integer>> permute(int[] nums) {// 初始化 used 数组,长度与 nums 一致,默认值为 falseused = new boolean[nums.length]; // 启动回溯过程backtracking(nums); return result;}// 回溯方法:构建全排列public void backtracking(int[] nums) {// 终止条件:当前排列长度等于数组长度,说明已生成一个完整排列if (answer.size() == nums.length) { // 将当前排列加入结果(注意:需新建 ArrayList 避免引用问题)result.add(new ArrayList<>(answer)); return;}// 遍历数组中的每个元素for (int i = 0; i < nums.length; i++) { // 跳过已使用的元素if (used[i]) { continue;}// 选择:将未使用的元素加入当前排列answer.add(nums[i]); // 标记为已使用used[i] = true; // 递归:处理下一个位置,继续构建排列backtracking(nums); // 回溯:移除最后加入的元素answer.remove(answer.size() - 1); // 标记为未使用,供后续选择used[i] = false; }}
}

(一)代码流程拆解

  1. 初始化
    • result 存储所有全排列结果;answer 存储当前正在构建的排列;used 数组标记元素是否已被使用。
  2. 启动回溯:调用 backtracking 方法,开始构建全排列。
  3. 回溯逻辑
    • 终止条件:当 answer 的长度等于 nums 的长度时,说明已生成一个完整排列,将其加入 result(需新建 ArrayList ,避免后续修改影响已存储的结果 )。
    • 选择与递归:遍历数组,跳过已使用的元素;将未使用的元素加入 answer,标记为已使用;递归调用 backtracking ,处理下一个位置。
    • 回溯:递归返回后,移除 answer 中最后加入的元素,标记为未使用,继续尝试其他元素。
  4. 返回结果result 存储所有全排列,作为方法返回值。

(二)关键逻辑解析

  • 状态标记的作用used 数组确保每个元素在一个排列中只使用一次,避免重复选择,保证排列的唯一性。
  • 回溯的本质:通过“选择 -> 递归 -> 回溯”的流程,枚举所有可能的排列组合。例如,生成 [1,2,3] 后,回溯移除 32,尝试 [1,3,2] ,以此类推。
  • 引用问题处理result.add(new ArrayList<>(answer)) 中,新建 ArrayList 存储当前 answer 的内容,避免后续 answer 的修改影响已存入的结果。

四、复杂度分析

(一)时间复杂度

  • 全排列的总数为 n!n 是数组长度 ),每个排列的生成需要 O(n) 的时间(构建列表、递归等 )。
  • 总体时间复杂度为 O(n × n!) ,主要受全排列数量和每个排列的处理时间影响。

(二)空间复杂度

  • 递归栈的深度最多为 n(生成一个排列需要递归 n 次 ),空间复杂度为 O(n)
  • result 存储所有排列,空间复杂度为 O(n × n!)answerused 数组的空间复杂度为 O(n)
  • 总体空间复杂度为 O(n × n!) ,主要受结果存储的影响。
http://www.xdnf.cn/news/1351621.html

相关文章:

  • 使用 PSRP 通过 SSH 建立 WinRM 隧道
  • Linux-常用文件IO函数
  • jQuery 知识点复习总览
  • (nice!!!)(LeetCode 面试经典 150 题) 173. 二叉搜索树迭代器 (栈)
  • 55 C++ 现代C++编程艺术4-元编程
  • 数据结构与算法-字符串、数组和广义表(String Array List)
  • 【Tech Arch】Apache Flume海量日志采集的高速公路
  • 解码LLM量化:深入剖析最常见8位与4位核心算法
  • Mac相册重复照片终结指南:技术流清理方案
  • chromadb使用hugging face模型时利用镜像网站下载注意事项
  • Node.js特训专栏-实战进阶:23. CI/CD流程搭建
  • 通过官方文档详解Ultralytics YOLO 开源工程-熟练使用 YOLO11实现分割、分类、旋转框检测和姿势估计(附测试代码)
  • 优先使用 `delete` 关键字删除函数,而不是将函数声明为 `private` 但不实现 (Effective Modern C++ 条款11)
  • 2025年Java在中国开发语言排名分析报告
  • 深度学习之PyTorch框架(安装,手写数字识别)
  • Redis 从入门到实践:Python操作指南与核心概念解析
  • Redis全面详解:从配置入门到实战应用
  • 联邦学习之----联邦批量归一化(FedBN)
  • 非线性规划学习笔记
  • 【KO】前端面试题一
  • 浮点数比较的致命陷阱与正确解法(精度问题)
  • 【Linux】深度学习Linux下的包管理器yum/apt
  • 自动化知识工作AI代理的工程与产品实现
  • 文献阅读笔记【物理信息神经网络】:Physics-informed neural networks: A deep learning framework...
  • 深入理解 Linux 系统文件 I/O:从 open 到重定向的底层逻辑》
  • CA6150主轴箱系统设计cad+设计说明书
  • Spring:IOC(控制反转 )、DI(依赖注入 )、AOP(通知类型、事务、拦截器)
  • 博士招生 | 美国圣地亚哥州立大学 Yifan Zhang 课题组博士招生,AI 安全领域顶尖平台等你加入!
  • ​崩坏世界观中的安全漏洞与哲学映射:从渗透测试视角解构虚拟秩序的脆弱性​
  • lanczso算法中的额外正交化代码解释