1,树层去重:
当题目给的集合中有重复的数字,但是要求结果不能相同,此时应该去重
有两种树层去重的方法:
方法一:当不需要考虑原集合的顺序时,可采用排序去重法
如例题:力扣90. 子集 II(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客
90.子集II :给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
- 输入: [1,2,2]
- 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
集合中有重复的元素,但是组合要求不能重复,需要去重(sort)
//去重需要排序
nums.sort()
.......
//同一层重复的元素剪枝
if(i>index&&nums[i]==nums[i-1]){
continue
}
方法二:当需要考虑原集合的顺序,不能打乱顺序,则不能使用排序法,可以用标记法
如例题:力扣491. 递增子序列(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客
491.递增子序列 :给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
- 输入: [4, 6, 7, 7]
- 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
用数组use标记每层元素的使用情况,如果使用了,则需要标记为true,则跳过此次循环
let use=[]
for(let i=index;i<nums.length;i++){
if(p.length>0&&nums[i]<p[p.length-1]){
continue
}
//如果没出现过为undefined
if(use[nums[i]]){
continue
}
//记录出现过的数
use[nums[i]]=true
p.push(nums[i])
dd(i+1)
p.pop()
}
}
2,树枝去重:
什么时候需要树枝去重:在全排列问题中,递归到下一层的选择范围总是整个集合,只需要排除掉已经加入path的节点,所以需要采用树枝的去重
树枝去重只有一种方法:标记法
如例题:力扣46. 全排列(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客
46.全排列:给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
因为是树枝去重,涉及递归,所以把use当作参数传递,当回溯的时候也要记得去除标记
for(let i=0;i<nums.length;i++){
//排除当前path中出现过的值
if(use[nums[i]]){
continue
}
p.push(nums[i])
use[nums[i]]=true
dd(use)
p.pop()
use[nums[i]]=false //回溯时,组合中出现过的值,需要去除标记
}
3,树层去重和树枝去重的双应用
如例题:力扣47. 全排列 II(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客
47.全排列 II :给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
- 输入:nums = [1,1,2]
- 输出: [[1,1,2], [1,2,1], [2,1,1]]
分析题目:集合中有重复的元素,且要求结果不能重复,需要树层去重(排序法),因为是全排列问题,每一层的选择范围都是整个集合(除了同一个树枝中使用过的元素),所以需要树枝去重(标记法)
什么时候可以跳过循环呢?
1,树层的跳出:当前元素和前一个元素相等,且use【i-1】值为false(撤销标记的记录,同层使用过相等的值,因为没有标记过的话,use的值应该为undefined),则continue
2,树枝去重:在递归中传递use,如果加入path则标记为true,当use为true时,continue
需要注意的是:本次保存在use中的是排序过后的集合的下标
for(let i=0;i<len;i++){
//数层去重
if(i>0&&nums[i]==nums[i-1]&&use[i-1]==false){
continue
}
//树枝去重
if(use[i]){
continue
}
use[i]=true
p.push(nums[i])
dd(use)
p.pop()
use[i]=false
}