题目描述
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
解题思路
- 回溯算法框架:
- 维护当前路径
path和结果集res - 遍历未使用元素,依次加入路径
- 递归探索下一层决策
- 回溯时移除最后选择的元素
- 维护当前路径
- 使用状态标记:
- 使用
used数组记录元素使用状态 - 避免同一元素重复使用
- 使用
- 终止条件:
- 路径长度等于原数组长度时保存结果
- 决策空间:
- 每层递归可选择的元素为未使用元素
- 决策树深度等于数组长度
关键洞察
- 决策树结构:
- 根节点为空路径
- 每层扩展所有未使用元素
- 叶子节点为完整排列
- 状态回溯机制:
- 递归返回时恢复
used状态 - 路径数组
pop()撤销选择
- 递归返回时恢复
- 无重复保证:
- 数组不含重复元素自然无重复排列
- 若有重复需额外去重(本题无需)
- 路径独立性:
- 每个递归层级维护独立路径副本
- 回溯时共享同一数组引用
复杂度分析
| 指标 | 说明 |
|---|---|
| 时间复杂度 | O(n × n!):存在 n! 种排列,生成每个排列需 O(n) 时间(递归树有 n! 叶子节点) |
| 空间复杂度 | O(n):递归栈深度 O(n) + used 数组 O(n) + 路径数组 O(n) |
注:n 为输入数组长度,n! 为排列总数
| 方法 | 时间复杂度 | 空间复杂度(额外) | 结果顺序 |
|---|---|---|---|
| 回溯+标记 | O(n × n!) | O(n) | 自然顺序 |
| 原地交换 | O(n × n!) | O(1) | 非字典序 |
代码实现
- JavaScript(原地交换)
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function (nums) {
const result = [];
const dfs = (nums, x) => {
if (x === nums.length - 1) {
result.push(Array.from(nums));
return;
}
for (let i = x; i < nums.length; i++) {
[nums[i], nums[x]] = [nums[x], nums[i]];
dfs(nums, x + 1);
[nums[i], nums[x]] = [nums[x], nums[i]];
}
}
dfs(nums, 0);
return result;
};
- python
def permute(nums: list) -> list[list]:
def backtrack(path, choices):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(choices)):
backtrack(path + [choices[i]], choices[:i] + choices[i+1:])
result = []
backtrack([], nums)
return result
- rust(回溯+标记)
pub fn permute(nums: Vec<i32>) -> Vec<Vec<i32>> {
fn backtrack(path: &mut Vec<i32>, choices: &[i32], result: &mut Vec<Vec<i32>>) {
if path.len() == choices.len() {
result.push(path.clone());
return;
}
for i in 0..choices.len() {
if path.contains(&choices[i]) { continue; }
path.push(choices[i]);
backtrack(path, choices, result);
path.pop();
}
}
let mut result = vec![];
backtrack(&mut vec![], &nums, &mut result);
result
}
实际应用场景
- 密码破解:字符排列组合生成字典`
- 数据测试:多参数组合测试用例生成`
- 游戏设计:棋盘布局生成`
- 路由规划:旅行商问题暴力求解基础`