题目:
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
抛砖引玉
在全排列中,处理过对一个数组的元素重新进行排列,但是全排列中限制了没有重复的元素: 全排列
本题中包含重复的元素,则在枚举的过程中,即使从不同位置取元素组合,也可能形成相同的组合。
那么本题的重点就成了如何避免形成重复的组合了:
- 记录选择是不能使用值作为记录的标记需要使用索引
- 重复元素合并参与索引选择-回溯时只参与一次:
- 注意此处并不会有元素不能被选中,例如两个连续的1:
- 第一个1在某次枚举时被选中,第二个1不再参与选择-回溯
- 第二个1形成的组合在第一个1不再某次被选中是触发(第二个1的选择-回溯)
回溯-记录选择
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
let _result = [],
inList = new Map()
function helper(item) {
if (item.length === nums.length) {
_result.push([...item])
return
}
for (let i = 0; i < nums.length; i++) {
// 增加重复元素判断,如果元素与上一个相邻元素相同,那么这两个相邻元素只有一个可以参与位置的选择-回溯
if (inList.has(i) || (i > 0 && nums[i] === nums[i - 1] && !inList.has(i-1))) continue
item.push(nums[i])
inList.set(i, true)
helper(item)
// 回溯-释放上面被在该位置选择的元素尝试在该位置选择其他元素
item.pop()
inList.delete(i)
}
}
if (nums.length < 2) return nums.length === 1 ? [nums]: _result
// 排序是重复元素相邻
nums.sort((a, b) => a - b)
helper([])
return _result
}
回溯-位置交换
- 在交换位置的逻辑中两次拿相同的元素与其他元素交互最后形成的组合是相同的
- 那么在每次拿一个元素枚举其他位置与其交换时,记录已经交换过的元素,如果再次遇到已经交换过的元素则不再交互
**注意:**上面交换重复的逻辑至少针对一轮交换,即针对一个索引位置的交换,更新要交换的位置后,去重的逻辑需要重新开始
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let _result = []
if (nums.length < 2) return nums.length === 1 ? [nums]: _result
function helper(index) {
if (index === nums.length) {
_result.push([...nums])
return
}
let map = new Map();
for (let i = index; i < nums.length; i++) {
// 一轮交换(一个新位置)中只有未交换过的元素才能参与交换
if(!map.has(nums[i])) {
map.set(nums[i], true);
// 逐个与当前索引位元素交换
swap(nums, i, index)
// 进行后续交换
helper(index + 1)
// 回溯上一次交换,让下一个位置的交换在上一次未交换的基础上开始
swap(nums, i, index)
}
}
}
// 替换指定位置两个数
function swap(a, i, j) {
var tmp = a[i]
a[i] = a[j]
a[j] = tmp
}
helper(0)
return _result
}
博客: 小书童博客
每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目 欢迎关注留言