「前端刷题」46. 全排列

76 阅读1分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

题目

给定一个不含重复数字的数组 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]]

 

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路

思路1(回溯算法)

总体思路是先创建一个特定的辅助函数backtracking()来完成回溯搜索(也可以理解为一种遍历吧)的操作。得益于JavaScript的特性,我们可以在主函数permute()内部定义这个函数,这样也更便于backtracking()使用resulttemp两个外部变量(相对于backtracking()来说)。其中result是用来存储最终的返回结果的,而temp是用来动态存储某一个全排列结果的。

backtracking()函数中的参数target就是待处理的数组对象(将要被初始化为nums),还用到了一个数组类型的变量used作为参数,是用于记录target中的数组元素的被使用情况,其元素为布尔类型(true代表该元素被使用过,下一层递归中不能再取用了,false代表该元素没被使用过,在下一层递归中是可用的)。

/**
 * @param {number[]} nums
 * @return {number[][]}
 */

var permute = function(nums) {
  let result = [];
  let temp = [];
  // 调用backtracking对nums进行回溯搜索(遍历)操作,将获得的全排列结果逐个存于result中
  // 其中used初始化为空数组,因为空数组中默认元素值为undefined,转化为布尔值即为false
  backtracking(nums, []);
  return result;

  function backtracking(target, used) {
  	// 递归的终止条件,当特temp的长度和target的长度相等时,说明得到了一个全排列结果
    if(temp.length === target.length) {
      // console.log('path-res:',path);	// 调试语句,可通过开发者工具查看过程
      // 若满足条件,则将temp中存储的那个全排列结果放入result,并用return结束一次递归
      // 注意这里不能简单地写作result.push(temp),否则result中只会存入一个空数组
      // 使用扩展运算符可以对temp进行一次深拷贝,当然用Array.from(temp)也可以
      // [...temp]可以理解为:[] = Array.of(...temp)
      result.push([...temp]);
      // console.log('result:',result);
      return;
    }

	// 这里的for循环可以理解为对要处理的数组的遍历
    for(let i = 0, length = target.length; i < length; i++) {
      // 如果该位元素已经被使用过了则跳过这次循环以防止在一次遍历中重复使用该元素
      // 这里的!!是确保used[i]被转换为布尔值,当然undefined默认就被转为false,
      if(!!used[i]) {
        continue;
      }
      // 如果该元素没被用过,则将其存入temp
      temp.push(target[i]);
      // 同时标志该位元素已被用过
      used[i] = true;
      // console.log('path-gen:',path,' ',used,i);	// 调试语句,可通过开发者工具查看过程
      // 用递归对剩余元素作遍历,其中used数组发挥关键作用,它让下一层递归知道哪些元素可用
      // 也就是告诉下一层递归数组中的哪些元素是所谓的【剩余元素】
      backtracking(target, used);
      // 将刚才存入temp的元素弹出,并更新元素使用状态,以确保能够正确完成“回溯”这一过程
      // 回溯算法的精髓就在就在于此,即:将状态恢复(回溯)到上一步
      temp.pop();
      used[i] = false;
      // console.log('path-bak:',path,' ',used,i);	// 调试语句,可通过开发者工具查看过程
    }
  }
};


提交记录
26 / 26 个通过测试用例
状态:通过
执行用时: 92 ms,在所有 JavaScript 提交中击败了82%的用户
内存消耗: 40.6 MB,在所有 JavaScript 提交中击败了61%的用户
时间:202172712:53:02

可以在Chrome浏览器中的开发者工具中观察运行过程,重点观察调用栈Call Stack的变化,体会递归过程。同时观察控制台输出,体会回溯过程。
46.全排列-开发者工具观察执行过程

注意:如果在上面那个调试界面看不到Console控制台,可以在该界面按下<kbd>Esc</kbd>键,或者点击按下图所示的菜单项:
在这里插入图片描述

同时也可以通过在线JS编辑器来运行调试,观察控制台输出。JS在线编辑器
jsrun.net