【算法29天:Day29】第七章回溯算法 LeetCode 全排列(46)

98 阅读2分钟

题目二:

image.png

解法一:

回溯三部曲

  • 递归函数参数

首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:

46.全排列

代码如下:

let result = [];
let path = [];
const backtracking (nums, used)
  • 递归终止条件

46.全排列

可以看出叶子节点,就是收割结果的地方。

那么什么时候,算是到达叶子节点呢?

当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

代码如下:

// 此时说明找到了一组
if (path.length == nums.length) {
    result.push(path);
    return;
}
  • 单层搜索的逻辑

这里和77.组合问题 (opens new window)131.切割问题 (opens new window)78.子集问题 (opens new window)最大的不同就是for循环里不用startIndex了。

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

代码如下:

for (int i = 0; i < nums.size(); i++) {
    if (used[i] == true) continue; // path里已经收录的元素,直接跳过
    used[i] = true;
    path.push(nums[i]);
    backtracking(nums, used);
    path.pop();
    used[i] = false;
}

完整代码如下:

var permute = function(nums) {
    let result = []
    let path = []
    let used = new Array(nums.length).fill(false)
    const backtracking = (nums, used) => {
        if (path.length === nums.length) {
            result.push([...path])
            return 
        }
        for (let i = 0; i < nums.length; i++) {
            if (used[i] === true) continue
            used[i] = true
            path.push(nums[i])
            backtracking(nums, used)
            path.pop()
            used[i] = false
        }
    }
    backtracking(nums, used)
    return result
};

总结

此时可以感受出排列问题的不同:

  • 每层都是从0开始搜索而不是startIndex,这样才能出现[3, 1, 2]
  • 需要used数组记录path里都放了哪些元素了

详细注释代码:

const permute = (nums) => {
  // 1. 设置结果集
  const result = [];

  // 2. 回溯
  const recursion = (path, set) => {
    // 2.1 设置回溯终止条件
    if (path.length === nums.length) {
      
      // 2.1.1 推入结果集
      result.push(path.concat());

      // 2.1.2 终止递归
      return;
    }

    // 2.2 遍历数组
    for (let i = 0; i < nums.length; i++) {

      // 2.2.1 必须是不存在 set 中的坐标
      if (!set.has(i)) {

        // 2.2.2 本地递归条件(用完记得删除)
        path.push(nums[i]);
        set.add(i);

        // 2.2.3 进一步递归
        recursion(path, set);

        // 2.2.4 回溯:撤回 2.2.2 的操作
        path.pop();
        set.delete(i);
      }
    }
  };
  recursion([], new Set());

  // 3. 返回结果
  return result;
};