小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
高中在学习排列组合的时候就学过全排列了,今天我们用程序来实现全排列。也就是说我们要将人类进行全排列的行为方式抽象成自动化的程序语言。这题我在 [快手一面] 中遇到过
这种全排列就是遍历出每一种可能,我们采用回溯算法来进行操作,其实这就是一个决策树的遍历过程
46. 全排列
给定一个不含重复数字的数组
nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
我们先举一个例子假设要进行1,2,3的全排列
我们首先在1,2,3中任选一个,假设是1,然后在剩下的2,3中任选一个,假设是2,最后剩下3,就选它就行了。我们画一棵【1,2,3】排列的决策树看看
我们来抽象一下,首先定义一个路径列表表示我们选择过的元素,然后定义一个选择列表,来表示我们还可以选择的元素,最后当到达叶子结点的时候,就表示遍历结束,返回当前的路径列表即可
图中 [1,2,3]
这种表示的是选择列表,绿色的数字表示的是每次的决策,叶子节点上的序列就是一个排列
我们要遍历出所有的排列结果,所以我们到达叶子结点拿到一个序列之后需要回溯,回溯就要放出自己刚刚决策的那个元素,放回选择列表中去。
这里的选择列表,我们定义为一个标记数组used
,用来标记每一个节点是否已经被选择,这样没有被选择的元素构成了选择列表
再具体的内容,看我代码中的注释,写的很是详细了~
【解法】回溯
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function (nums) {
// 保存结果数组,保存每个路径(排列)
const result = [];
// 调用回溯函数,传入参数
backtracking(nums, nums.length, [], []);
// 返回结果数组
return result;
// 定义回溯递归函数,传入数组,长度,节点是否被使用过的数组
// used 用来标记节点是否被用过 path 用来存储路径,定义为一个栈
function backtracking(nums, len, used, path) {
// 递归出口
// 如果到达叶子节点,将路径推入结果数组,并返回
if (path.length === len) {
result.push([...path]);
return;
}
// 遍历候选字符
for (let i = 0; i < len; i++) {
// 使用过就下一轮循环
if (!used[i]) {
// undefind和fasle都会进来
// 这里说明这个数还没有被使用,入栈path
path.push(nums[i]);
// 标记这个数被使用过了
used[i] = true;
// 开始进行递归
backtracking(nums, len, used, path);
// 回溯【状态重置】撤销之前的操作
path.pop();
used[i] = false;
}
}
}
};
最后我们看一下结果