持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例
输入:nums = [1, 2, 3]
输出:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
思路:广度优先
排列是讲究顺序的,不同的顺序会产生不同的排列
模拟搜索过程
- 以 1 开头的排列,它们是:
[1, 2, 3], [1, 3, 2]
- 以 2 开头的排列,它们是:
[2, 1, 3], [2, 3, 1]
- 以 3 开头的排列,它们是:
[3, 1, 2], [3, 2, 1]
为了做到不重不漏
- 我们需要按顺序枚举每一个位置可能出现的数字
- 之前已经出现的数字,在接下来要选择的数字中不能再出现
- 这样的问题,我们可以将其画成一颗树的样子
全排列的树形结构
以输入数组 [1, 2, 3]
为例
- 开始排列为空列表(数组)
- 第一个位置有三种可能,分别是 1、2、3
[]
/ | \
[1] [2] [3]
- 由于第一个位置已经使用了一个数字,那么第二个位置可以使用的数字就只剩两个了
因此,这一层的节点又可以展开两个分支
[]
/ | \
[1] [2] [3]
/ \ / \ / \
[1,2] [1,3] [2,1] [2,3] [3,1] [3,2]
- 选出了两个之后,由于一共就只有三个数字供我们选择,因此最后一个位置上的数字就是唯一确定的了
在这些叶子节点中的全部排列,就是 [1,2,3]
的全排列
[]
/ | \
[1] [2] [3]
/ \ / \ / \
[1,2] [1,3] [2,1] [2,3] [3,1] [3,2]
| | | | | |
[1,2,3] [1,3,2] [2,1,3] [2,3,1] [3,1,2] [3,2,1]
思路:深度优先
- 先选第一个数 1
[]
/
[1]
- 再选第二个数 2
[]
/
[1]
|
[1,2]
- 最后填上唯一剩下的 3
[]
/
[1]
|
[1,2]
|
[1,2,3]
- 之后进行回退,撤销对 3 的选择,回到
[1,2]
这个节点 - 由于这个阶段的只有数字 3 且已经处理过了,因此撤销对 2 的选择,继续回退到
[1]
- 在这个阶段有两个选择(2 或 3),2 我们已经走过了,接下来选择 3
- 选择 3 之后,接下来可以选择的又只有 2,此时又可以得到排列
[1,3,2]
[]
/
[1]
/ \
[1,2] [1,3]
| |
[1,2,3] [1,3,2]
- 以此类推,走完全部的选择,就可以得到最终的全排列
每一个节点标识了求解问题的不同阶段,可以利用变量不同的值来指定,称之为状态
深度优先遍历在回到上一层节点时需要 状态重置
代码实现:深度优先
var permute = function(nums) {
const res = [], path = []
backtracking(nums, nums.length, []) // 调用回溯函数,传入 nums,nums长度,used数组
return res
function backtracking(n, k, used) {
if(path.length === k) { // 递归终止条件:已使用长度和原数组长度一致,说明原数组中的元素已经全部使用了
res.push(Array.from(path))
return
}
for(let i=0; i<k; i++) {
if(used[i]) continue // 已经使用过了就跳过
path.push(n[i]) // 插入最近一个没使用过的元素
used[i] = true // 将这个元素的状态设置为“已使用”
backtracking(n, k, used) // 递归
path.pop() // 回溯,将push的元素pop出来
used[i] = false // 然后标记为“未使用”,继续其他分支
}
}
}
\