[路飞]_全排列 II

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

47. 全排列 II

题目

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例1

输入: nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]
复制代码

示例2

输入: nums = [1,2,3]
输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
复制代码

题解

递归 + 回溯 + 剪枝

假如数组 n u m s = [ 1 , 2 , 3 ] nums = [1,2,3]

递归回溯所有可能结果;可以参考下图

graph TD

1 --> 1,1 & 1,2 & 1,3
1,1 --> 1,1,1 & 1,1,2 & 1,1,3
1,2 --> 1,2,1 & 1,2,2 & 1,2,3
1,3 --> 1,3,1 & 1,3,2 & 1,3,3
2 --> 2,1 & 2,2 & 2,3
2,1 --> 2,1,1 & 2,1,2 & 2,1,3
2,2 --> 2,2,1 & 2,2,2 & 2,2,3
2,3 --> 2,3,1 & 2,3,2 & 2,3,3
3 --> 3,1 & 3,2 & 3,3
3,1 --> 3,1,1 & 3,1,2 & 3,1,3
3,2 --> 3,2,1 & 3,2,2 & 3,2,3
3,3 --> 3,3,1 & 3,3,2 & 3,3,3

递归回溯实现可以先写一份极简版代码,代码中有注释,理解起来不是很难?

var permute = function(nums) {
    // 数组长度
  const len = nums.length;
  // 递归
  dfs([]);
  function dfs(path) {
      // 递归出口
    if (path.length === len)return
    for (let i = 0; i < len; i++) {
        // 统计路径上的元素并放入路径数组
        path.push(nums[i]);
        dfs(path);
        // 回溯
        path.pop();
    }
  }
  
};
复制代码

如果仅仅是这份极简版代码,实现的是全排列比如 [ 1 , 1 , 1 ] [1,1,1] 这类的数据也会统计到,明显这种分支并不是我们想要的,如何将这种数据排除的或者在递归回溯的时候将这些分支【裁剪】掉?

增加条件

var permute = function(nums) {
    // 数组长度
  const len = nums.length;
  // 递归
  dfs([]);
  function dfs(path) {
      // 递归出口
    if (path.length === len)return
    for (let i = 0; i < len; i++) {
        // 统计路径上的元素并放入路径数组
       if(isCheck()){
            path.push(nums[i]);
            dfs(path);
            // 回溯
            path.pop();
        }
    }
  }
  
};
复制代码

假设我们在分支递归的过程中,增加条件 i s C h e c k isCheck , 只有符合条件的分支才允许进入下一步递归,那么是不是就可以时间【剪枝】这个功能了

isCheck如何实现?

分析:

  • 数组有重复元素,
  • 组成不重复的全排列

有重复元素,需要排序,如果元素前后两个元素相同,可能这个分支要被剪除,但是哪个分支被剪除呢?这里有需要一个额外空间存储之前选中的元素

var permute = function(nums) {
    // 数组长度
  const len = nums.length;
  // 之前选中的元素
   const list = Array(len).fill(0)
  // 递归
  dfs([]);
  function dfs(path) {
      // 递归出口
    if (path.length === len)return
    for (let i = 0; i < len; i++) {
        // 统计路径上的元素并放入路径数组
       if(isCheck()){
           list[i]  = 1 ; //当前元素已经选中
            path.push(nums[i]);
            dfs(path);
            // 回溯
            path.pop();
            list[i]  = 1 ;// 回溯
        }
    }
  }
  
};
复制代码

所以 i s C h e c k isCheck 方法要判断,

  • 当前元素之前是否选过,
  • 当前元素与上一位元素是否相等?
    • 相同,但是上一位元素没用到,当前元素可用
    • 相同,但是上一位元素已经用过了,当前元素不可用
  • 以上两个条件都不满足,当前元素可用

isCheck 代码

 function isCheck(i) {
    if (list[i] === 1) return false
    if (i > 0 && nums[i] === nums[i - 1] && list[i - 1] === 0) return false
    return true
  }
复制代码

根据上述思路编辑代码如下:

完整代码如下

var permuteUnique = function (nums) {
  const len = nums.length
  nums.sort((a, b) => a - b)
  const result = []
  const list = Array(len).fill(0)
  dfs([])
  return result
  function dfs(path) {
    if (path.length === len) {
      result.push([...path])
      return
    }

    for (let i = 0; i < len; i++) {
      if (isCheck(i)) {
        path.push(nums[i])
        list[i] = 1
        dfs(path)
        path.pop()
        list[i] = 0
      }
    }
  }

  function isCheck(i) {
    if (list[i] === 1) return false
    if (i > 0 && nums[i] === nums[i - 1] && list[i - 1] === 0) return false
    return true
  }
}

复制代码

猜你喜欢

转载自juejin.im/post/7063422383822274567
今日推荐