题目链接:
https://leetcode.cn/problems/subsets-ii/
记录一个力扣题思考:
在一个可能重复的数组中不放回的取出子集,其中子集中不能出现重复。

依然是组合题,毫无疑问用回溯法可以选出所有的组合,但是在选取组合的过程中,这个题有两个关键点:
①是不能出现重复,即在递归中需要一个参数startIndex,来不断地调整元素选取的开始位置。
②是在可能出现重复的数组中选,这就意味着在同一层的选取中碰到重复的元素需要跳过即去重操作,我一开始是想用下面的代码进行去重,最后发现这样会多去了在同一树枝上的重复选取的情况(而在同一树枝上出现重复是不用进行去重操作的,因为题目是允许一个子集中出现重复元素的):
if (i > 0) {
if (nums[i] == nums[i - 1] ) {
continue;
}
}
这行代码为什么会把同一个树枝上的也去掉呢?
举个例子,如在数组nums [1,2,2,3] 中选组合,那么在已经选出path =[1,2] 的情况下,stratIndex = 1, i = 1, 当递归进入下一层选取时(递归即进入下一层的选取,在树形图上就是树枝中),stratIndex =2, i = 2,会碰到nums[2]这个元素,而这个时候,if (nums[i] == nums[i - 1] )是满足的,因此这个时候会在这一层中直接continue掉nums[2]这个元素,进入选取下一个元素即 i = 3 ,nums[3] = 3的环节。这个时候原本可以选出组合[1,2,2] [1,2,2,3]的情况直接被忽略掉(当然不止是这一种情况)。

这个时候怎么样在代码中区分 去掉同一层 和 去掉同一树枝 呢?
注意到同一层的每次选取后进入下一个元素的选取时都会进行回溯操作,而进入下一层即递归不会对这次元素选取的操作进行回溯这个特点,用一个标记Boolean数组used[] ,在每一次元素出现时,进行标记——used[i] = true,进入同一层的对used数组进行回溯操作——used[i] = false,而进入下一层的(即进入递归)就不会对这个元素标记进行回溯,used[i] 还是 true 。
这个时候在同一层中重复出现和在下一层即同一个树枝中重复出现的use[i] 就不一样了。即若是在同一层中再一次出现了,used[i - 1]是false,而下一层中出现重复 used[i - 1]是 true。则就有下面的代码判断来实现仅仅只对同一层进行去重:
// 同一层去重
if (i > 0) {
if (nums[i] == nums[i - 1] && used[i - 1] == false) {// 这一个条件只有在同一层中出现重复时满足,就是为了区分同一层和同一树枝选取重复元素的情况
continue;
}
}
下面贴出完整代码:
class Solution {
// 选子集-组合题-用回溯
// 审题:数组可能重复,但是选出来的子集不能重复,实现这句话要满足两个条件,①同一树层选取元素时需要去重操作;②同一树层选元素是,下一个元素的选取得+1
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);// 组合去重前对数组进行排序
used = new boolean[nums.length];
Arrays.fill(used, false);
backingtracking(nums, used, 0);
return result;
}
public void backingtracking(int[] nums, boolean[] used, int startIndex) {
result.add(new ArrayList<>(path));
if (startIndex == nums.length) {
return;
}
for (int i = startIndex; i < nums.length; i++) {
// 同一层去重
if (i > 0) {
if (nums[i] == nums[i - 1] && used[i - 1] == false) {// 这一个条件只有在同一层中出现重复时满足,就是为了区分同一层和同一树枝选取重复元素的情况
continue;
}
}
path.add(nums[i]);
used[i] = true;
backingtracking(nums, used, i + 1);
path.remove(path.size() - 1);// 回溯path
used[i] = false;// 同一层下次选取元素回溯used数组
}
}
}