news 2026/10/6 23:57:17

回溯法详解:LeetCode 46. 全排列

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
回溯法详解: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]]

二、 核心思路:回溯 (Backtracking)

全排列问题是回溯算法最经典的入门题。回溯算法的本质是决策树的深度优先遍历 (DFS)。

我们可以将全排列问题具象化为:有n个空位,我们需要从左到右依次填入nums中的数字。每个数字只能用一次。

1. 状态定义

在遍历决策树的过程中,我们需要维护以下几个关键状态:

  • 路径 (Path):记录当前已经做出的选择(即当前正在生成的排列)。

  • 选择列表:当前还可以做出的选择(即数组中还未被使用的数字)。

  • 结束条件:到达决策树底层,无法再做选择的条件。

2. 关键难点:如何记录“已使用”?

由于数组中的元素不能重复使用,我们需要一个标记机制。最直接的方法是引入一个布尔数组used:

  • used[i] == true:表示nums[i]已经在当前路径中被使用。

  • used[i] == false:表示nums[i]尚未被使用,可以加入当前路径。

3. 回溯过程图解

以nums = [1, 2, 3]为例,决策树的遍历过程如下:

  1. 第一层:可以选择 1, 2, 3。

  2. 第二层:如果第一层选了 1,第二层只能从 2, 3 中选。

  3. 第三层:如果前两层选了 1, 2,第三层只能选 3。

  4. 当路径长度达到 3 时,记录当前路径,并撤销上一步选择(回溯),返回上一层尝试其他分支。

三、 代码实现 (C++)

基于上述思路,我们可以写出非常标准的回溯代码模板:

class Solution { public: vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> result; vector<int> path; vector<bool> used(nums.size(), false); backtrack(nums, used, path, result); return result; } private: void backtrack(const vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& result) { // 1. 终止条件:路径长度等于数组长度,说明找到了一个全排列 if (path.size() == nums.size()) { result.push_back(path); return; } // 2. 遍历选择列表 for (int i = 0; i < nums.size(); ++i) { // 排除不合法的选择:当前数字已被使用 if (used[i]) continue; // 3. 做选择 used[i] = true; path.push_back(nums[i]); // 4. 进入下一层决策树 backtrack(nums, used, path, result); // 5. 撤销选择 (回溯核心) path.pop_back(); used[i] = false; } } };

四、 复杂度分析

  • 时间复杂度:O(n×n!)

    • 总共有 n! 种全排列。

    • 每种排列需要 O(n) 的时间将其从path复制到result中。

    • 递归树的节点总数约为 e×n!,因此整体时间复杂度为 O(n×n!)。

  • 空间复杂度:O(n)

    • 递归调用栈的深度最大为 n。

    • used数组和path数组的空间开销均为 O(n)。

    • 注意:返回结果result占用的空间不计入算法的辅助空间复杂度。

五、 拓展与普适化:回溯算法通用模板

全排列问题揭示了回溯算法解决“组合/排列/子集”类问题的通用范式。我们可以将其抽象为以下伪代码模板:

result = [] def backtrack(路径, 选择列表): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 新的选择列表) 撤销选择
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 23:55:24

DeepSeek LeetCode 236. 二叉树的最近公共祖先 Java实现

LeetCode 236. 二叉树的最近公共祖先 Java 实现 思路&#xff1a;递归后序遍历 从根节点开始递归&#xff1a;如果当前节点为空&#xff0c;或当前节点就是 p 或 q&#xff0c;直接返回当前节点。分别递归左右子树&#xff0c;得到 left 和 right。如果 left 和 right 都…

作者头像 李华
网站建设 2026/10/6 23:35:19

74LS181运算器实验全解析:从引脚功能到电路连线的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 23:35:17

自举电容:Buck电路浮地驱动与持续导通的灵魂

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 23:33:18

ROS2 Humble + Gazebo 11 机械臂仿真:从URDF到ros2_control完整配置指南

1. 为什么要在Gazebo里折腾机械臂仿真 如果你正在做机械臂相关的开发&#xff0c;不管是毕业设计、课题研究还是产品预研&#xff0c;大概率会遇到一个很现实的问题&#xff1a;真机太贵、太占地方&#xff0c;而且调试过程中一旦参数写错&#xff0c;轻则撞机重则烧电机。我见…

作者头像 李华
网站建设 2026/10/6 23:31:49

Gerrit+Repo企业级代码协同实战:从零搭建生产可用代码门禁系统

简介&#xff1a;本资源是一份面向Linux系统管理员与DevOps工程师的Git代码协作平台搭建实战指南&#xff0c;聚焦于整合Git、Repo与Gerrit构建企业级代码托管与评审环境。内容覆盖从基础依赖安装&#xff08;Git、OpenSSH、Python setuptools&#xff09;、Gitosis权限管理初始…

作者头像 李华