「LeetCode」47-全排列II

110 阅读1分钟

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

一.题目:

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]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

二、思路分析:

这道题目看似与我之前写过的一篇文章全排列完全相似,但是这道题明显是之前那道题的扩充版,之前那道题目给出的是不重复的元素,所以实现起来很简单,只需要利用回溯不断的遍历数组即可。而这道题目给出的元素可以是重复的,这就使得我们的答案不能够完全实现所有元素全排列。

所以这道题目需要做的是:

  1. 回溯的时候不能够选择已经选过的元素,用used数组来标识
  2. 我们需要对同一层的重复元素进行剪枝,比如[1,1,2]这个数组,我们不能需要对第二个1进行剪枝,因为它与第一个1值是重复的,解决这种问题的关键点就是保持重复元素的相对位置,只要能够保证重复元素在结果集的相对位置不变,那么这个答案就是合理的,所以我们在判断当前元素是否与同一层的前一个元素相同的时候,还要判断前一个元素是否被选中,如果没有被选中,那么当前元素也不能选择,如果选择会破坏它的相对位置

三、代码:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permuteUnique = function(nums) {
    let track = []
    let res = []
    nums.sort((a,b) => a-b)
    let n = nums.length
    let used = new Array(n).fill(false)
    if(n == 1) return[nums]
    const backtrack = function(nums,n){
        //base case
        if(track.length == n){
            res.push(track.slice())
            return 
        }
        for(let i = 0 ; i < n ; i++){
            if(used[i]) continue
            //剪枝操作
            if(i>0 && nums[i] == nums[i-1] && !used[i-1]) continue
            //选择
            track.push(nums[i])
            used[i] = true
            backtrack(nums,n)
            track.pop()
            used[i] = false
        }
    }
    backtrack(nums,n)
    return res 
};

四、总结:

这道回溯题目的难点在于如何进行剪枝操作,需要考虑到同一层重复元素如何处理,所以我们在做题目的时候一定要思考到各种特殊情况,利用已知去推未知。