(LeetCodeHot100)46. 全排列——permutations
(LeetCodeHot100)46. 全排列——permutations
zhangzhang46. 全排列——permutations
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
1 | 输入:nums = [1,2,3] |
示例 2:
1 | 输入:nums = [0,1] |
示例 3:
1 | 输入:nums = [1] |
提示:
1 <= nums.length <= 6-10 <= nums[i] <= 10nums中的所有整数 互不相同
我的正确答案:DFS + 回溯
- 一遍过(就是复杂度高了点):
- 时间:击败15.48%
- 空间:击败29.46%
1 | class Solution { |
官方解答:DFS + 回溯 + 状态标记
- 时间复杂度好多了:击败96.58%
- 空间差不多:击败29.82%
1 | import java.util.ArrayList; |
一、官方解法核心思路(DFS + 回溯 + 状态标记)
官方解法采用深度优先搜索(DFS)+ 回溯,核心是通过 boolean[] used 数组标记元素是否被使用,避免重复选择,从而生成所有全排列,步骤拆解:
1. 核心变量说明
path:保存当前正在构建的排列(比如 [1,2] 是构建 [1,2,3] 的中间状态);used:长度和nums一致的布尔数组,used[i] = true表示nums[i]已被加入path,避免重复选;depth:当前path的长度(递归深度),当depth == nums.length时,说明已生成一个完整排列,加入结果集;res:最终保存所有全排列的结果集。
2. 递归逻辑(DFS + 回溯)
1 | ① 终止条件:depth == nums.length → 保存当前排列,返回; |
3. 举例(nums = [1,2,3])
- 第一层递归(depth=0):选 1 → used [0]=true,path=[1];
- 第二层递归(depth=1):选 2 → used [1]=true,path=[1,2];
- 第三层递归(depth=2):选 3 → used [2]=true,path=[1,2,3] → 保存到 res;
- 回溯:删除 3,used [2]=false → 回到第二层,无其他未用元素 → 再回溯,删除 2,used [1]=false;
- 第二层重新选 3 → used [2]=true,path=[1,3] → 第三层选 2 → 生成 [1,3,2];
- 以此类推,遍历所有可能的选择,生成全排列。
二、为什么官方解法时间复杂度更优?
你的解法和官方解法的时间复杂度量级都是 O (n×n!)(n! 是全排列总数,每个排列需 O (n) 时间构建),但实际运行效率官方远优,核心差异在「元素是否被使用的判断效率」:
| 维度 | 你的解法(list.contains ()) | 官方解法(used [] 数组) |
|---|---|---|
| 判断元素是否已选 | 遍历 list 逐个比较,O (n) 时间 | 直接访问数组下标,O (1) 时间 |
| 单轮递归的时间成本 | O(n) × O(n) = O(n²) | O(n) × O(1) = O(n) |
| 总时间复杂度(实际) | O(n² × n!) | O(n × n!) |
关键细节拆解:
你的解法的性能瓶颈:
你用
!list.contains(nums[i])判断元素是否已加入排列,contains()是线性查找 —— 每次判断都要遍历list中已有的depth个元素(最坏 O (n))。比如生成 [1,2,3] 时:
选第一个元素:list 为空,contains 是 O (1);
选第二个元素:list 有 1 个元素,contains 是 O (1);
选第三个元素:list 有 2 个元素,contains 是 O (2);
整体单条排列的判断成本是 O (1+1+2) = O (n),叠加 n! 个排列,总时间是 O (n² × n!)。
官方解法的优化核心:
用
boolean[] used数组标记状态 —— 数组的下标对应nums的下标,判断used[i]只需直接访问内存地址,是 O (1) 常数时间。比如判断
nums[2]是否已选,只需看used[2]的值,无需遍历,单条排列的判断成本是 O (n × 1) = O (n),总时间是 O (n × n!)。
举个直观例子(n=6,题目上限):
你的解法:n² × n! = 36 × 720 = 25920 次操作;
官方解法:n × n! = 6 × 720 = 4320 次操作;
前者操作数是后者的 6 倍,n 越大,差距越明显(比如 n=5 时,前者是 25×120=3000,后者是 5×120=600)。
三、补充说明(时间复杂度的 “量级” vs “实际”)
从算法复杂度的「量级」来说,两者都属于 O (n×n!)(因为 n² × n! 渐进等价于 n×n!),但渐进等价不代表实际效率相同—— 官方解法的「常数项更小」,这是工程层面的关键优化。


