持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第40天,点击查看活动详情.
46. 全排列
题目描述
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
解题思路
思路一: 回溯
我们定义递归函数 dfs(nums, len, depth, path, used, res)
表示从左往右填到第 depth 个位置,当前排列为 path。 那么整个递归函数分为两个情况:
-
如果 depth === nums.length,说明我们已经填完了 nums.length 个位置(注意下标从 0 开始),找到了一个可行的解,我们将 path 放入答案数组中,递归结束。
-
如果 depth < nums.length,我们要考虑这第 depth 个位置我们要填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记
used
来标记已经填过的数,那么在填第 depth 个数的时候我们遍历题目给定的 length 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数dfs(nums, len, depth + 1, path, used, res)
。回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数
实现代码如下:
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let len = nums.length,
res = [],
// 判断数字是否被选择过
used = {};
dfs(nums, len, 0, [], used, res);
return res;
};
function dfs(nums, len, depth, path, used, res) {
if (depth == len) {
res.push([...path]);
return;
}
for (let i = 0; i < len; i++) {
if (!used[i]) {
path.push(nums[i]);
used[i] = true;
// 递归
dfs(nums, len, depth + 1, path, used, res);
used[i] = false;
path.pop();
}
}
}
-
时间复杂度: , n 为序列的长度
dfs调用次数分析:
在第 1 层,结点个数为 n 个数选 1 个的排列,时间复杂度为 ; 在第 2 层,结点个数为 n 个数选 1 个的排列,时间复杂度为 ;
...
故有:
总时间复杂度为: -
空间复杂度: , 需要额外的空间且该空间取决于递归的深度
小结:
回溯框架: for循环中递归回溯,回溯前选择元素push到路径,回溯结束从路径中pop掉
由于全排列是没有顺序的,因此不设start参数,也正因此,回溯前需判断路径中是否已经存在,存在的话
则continue继续for,否则选择->递归回溯->撤销选择