leetcode 46 全排列 JavaScript
思路:
全排列的主要思想是回溯,递归过程使用的是dfs,只不过在dfs的过程中会涉及到对已使用的数进行标记,标记后递归,再把标记清除的一个过程
add:
时隔多个月再次回顾,毕竟dfs 回溯,是本人弱项
1、首先考虑一下什么时候结束一次循环 => 填满一次nums长度的数组
比如nums是[1,2,3], 自己循环时填的数组path为[],完成一次填满path的操作
2、填数组时应该做什么操作
(1)判断是否使用过该数字,如已使用,continue,因为path里的数不能重复
(2)如果没使用过,push进path中,并标记该数字已使用
(3)递归,同时考虑递归的结束判断 =>当path填满nums长度之后(也就是1提到的),将填满的path加到最后返回结果数组res中,之后结束本次递归
(4)回溯,最难懂的一步
为了搞懂这个,我从递归流程中分析了一遍
dfs([1])->dfs([1,2])->dfs([1,2,3])->return 此时used=1,2,3 同时由于满足递归条件,res得到了首个组装完成的path 也就是目前res=[[1,2,3]],此时path=[1,2,3]的递归已经停止
轮到path=[1,2]的递归执行下一步了,也就是path.pop(),同时used[2] = false 此时used=1,2,path=[1],
此时到下一个循环,也就是3,所以会产生[1,3]的path继续递归 最终得到[1,3,2]递归结束
轮到path=[1]执行pop,used[1] = false,此时path=[],used也清空了
ok第一次for循环结束,轮到2先进场…
推算到这里,如果没理解错的话,回溯其实就是为下一次循环清空要复用的path和used,最主要的还是第(3)步递归的流程
妙,一看就会,一写就废 g
代码如下:
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
const res = [];
const used = {};
function dfs(path) {
if (path.length == nums.length) { // 递归完成
res.push([...path]); // 拷贝path
return;
}
for (let num of nums) {
if (used[num]) continue; // 使用过该数
path.push(num); // 放入path
used[num] = true; // 已使用
dfs(path); // 选了数之后递归
path.pop(); // 回溯上一步 同时要把刚刚用了的数设为false
used[num] = false; // 重设为未使用
}
}
dfs([]);
return res;
};
这里涉及到一个path的拷贝,因为传入的参数是引用数据类型,如果不进行拷贝,那么path始终指向堆中同一个地址空间,即便后面push到res中,res里面实际上存的也是它在栈中的引用,只要被修改,对应的值全部都改变,最后会发现结果全为空数组。