对于列举所有可能性的这种题目,一般就是要回溯了,一棵树,然后列举所有的可能性,然后根据题目要求,把树上的枝叶裁剪,得到题目所需要的结果
这一题是一个比较典型的回溯题目,按照代码来,也是深度优先遍历DFS
回溯 + 标记
var permute = function (nums) {
let res = []
// 标记元素是否使用过
let used = new Array(nums.length).fill(false)
// 这里的used也没有必要传入
var dfs = function (used, one) {
if (one.length === nums.length) {
res.push([...one])
return
}
for (let i = 0; i < nums.length; ++i) {
// 裁剪枝叶,如果已经使用过,那么就到下一元素去
if (used[i]) continue
// 标记已经使用过的元素
used[i] = true
one.push(nums[i])
dfs(used, one)
// 回溯
used[i] = false
one.pop()
}
}
dfs(used, [])
return res
};
✅ 思路说明:
- 每一层递归代表当前正在选择排列的第
k位; - 使用
used数组来标记哪些元素已经被使用; - 每当
path长度达到nums.length,就说明得到了一个完整排列; - 回溯的核心在于“尝试 → 回退”,恢复现场以探索下一种可能性。
Swap
一种swap的解法
var permute = function (nums) {
let res = []
var dfs = function (start) {
if (start === nums.length) {
res.push([...nums])
return
}
// 一般来说,这里的 start 的改变,就是一种剪枝
for (let i = start; i < nums.length; ++i) {
[nums[i], nums[start]] = [nums[start], nums[i]]
dfs(start + 1); // 注意这里的分号
[nums[start], nums[i]] = [nums[i], nums[start]]
}
}
dfs(0)
return res
};
✅ 特点:
- 无需
used数组,直接对nums原地操作; - 每轮固定一位,继续对后续元素进行排列;
- 回溯通过交换复原现场;
- 空间开销更小,逻辑也更紧凑。
错误代码
下面是错误的代码
可以看到上面的一些注释,对于一些回溯的考点,也是在这里做出一点改变即可
这里我最主要出现的错误就是,对于哪些使用过的元素,是没有感知的,所以就需要一个缓存来做这件事
var permute = function (nums) {
let res = []
var dfs = function (start, one) {
if (one.length === nums.length) {
res.push([...one])
return
}
// 一般来说,这里的 start 的改变,就是一种剪枝
for (let i = start; i < nums.length; ++i) {
one.push(nums[i])
// 这里 i 的起始点也是有考究的
dfs(i, one)
one.pop()
}
}
dfs(0, [])
return res
};