一起养成写作习惯!这是我参与「掘金日新计划 · 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
二、思路分析:
这道题目看似与我之前写过的一篇文章全排列完全相似,但是这道题明显是之前那道题的扩充版,之前那道题目给出的是不重复的元素,所以实现起来很简单,只需要利用回溯不断的遍历数组即可。而这道题目给出的元素可以是重复的,这就使得我们的答案不能够完全实现所有元素全排列。
所以这道题目需要做的是:
- 回溯的时候不能够选择已经选过的元素,用
used数组来标识 - 我们需要对同一层的重复元素进行剪枝,比如
[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
};
四、总结:
这道回溯题目的难点在于如何进行剪枝操作,需要考虑到同一层重复元素如何处理,所以我们在做题目的时候一定要思考到各种特殊情况,利用已知去推未知。