ts算法题解(第40天)---leetcode 47.全排列II

128 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

前言

每天一道算法题,死磕算法

题目

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

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

分析

我们先上排列的模板

function backTrack(track:number[],nums:number[],result):void{
  if(track.length === nums.length){
    result.push(track.slice());
    return;
  }
  for(let i=0;i<nums.length;i++){
    // 剪枝操作
    track.push(nums[i]);
    backTrack(track,nums,result);
    track.pop();
  }
}

我们在建立一下条件反射,一看到有重复数字,我们
第一要做的就是排序,
第二要做的就是剪枝,防止有重复的track

排序很好做,我们主要讲剪枝,我们看一下以前的 js算法题解(第二十天)---leetcode-46. 全排列的解法,他是减掉重复的i值得树枝, 比如第一行i为0,那么第二行,我们就去掉i为0的树枝
他使用的剪枝的方法是

// 筛选没有放入路径的选择 
if(track.includes(nums[i])){ 
    continue;
}

但是这种对于有重复元素的数组不适用。

所以我们要使用另一种方法,加哨兵,这个哨兵是一个数组,初始化所有值都是false,如果当前索引填入了值,那么值变为true

代码如下



function permuteUnique(nums: number[]): number[][] {
  let result = [];
  let track = [];
  let used = new Array(nums.length).fill(false);
  nums = nums.sort();
  function backTrack(track,used):void{
    if(track.length === nums.length){
      result.push(track.slice());
      return;
    }
    for(let i=0;i<nums.length;i++){
      if(used[i]){
        continue;
      }
      track.push(nums[i]);
      used[i] = true;
      backTrack(track,used);
      used[i] = false;
      track.pop();
    }
  }
  backTrack(track,used);
  return result;
};

此时就满足了全排列I的结果
因为我们有重复的元素,所以我们还需要继续剪枝

我们先来观察一下,比如[1,1,2]这个数组,使用上面的代码排列出来以后会出现两个[2(2),1(0),1(1)],[2(2),1(1),1(0)](其中小括号里面的是索引)

首先我们要判断两个值是否相等,i>0&&nums[i]===nums[i-1] 这样子放进代码里肯定也是不行的,他的效果和

// 筛选没有放入路径的选择 
if(track.includes(nums[i])){ 
    continue;
}

类似,我们还得加一个索引判断!used[i-1]就是说,如果两个值相同,但是索引低的值还未填入,那么我们就不要了,这样子相当于,我们去掉了[2(2),1(1),1(0)]这个结果

题解

function permuteUnique(nums: number[]): number[][] {
  let result = [];
  // 路径
  let track = [];
  let used = new Array(nums.length).fill(false);
  nums = nums.sort();
  function backTrack(track,used):void{
    if(track.length === nums.length){
      result.push(track.slice());
      return;
    }
    for(let i=0;i<nums.length;i++){
      // i > 0 && nums[i] === nums[i-1]这个逻辑是不是很熟悉,组合和子集也用到了这个,只不过组合/子集中用的是i>start,排列中用的是i>0
      if(used[i] || (i > 0 && nums[i] === nums[i-1])){
        continue;
      }
      track.push(nums[i]);
      used[i] = true;
      backTrack(track,used);
      used[i] = false;
      track.pop();
    }
  }
  backTrack(track,used);
  return result;
};

参考

- 回溯算法秒杀所有排列/组合/子集问题