给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
Input:nums = [1,2,2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]
问题分析:
这题和子集非常类似,只不过子集中没有重复的元素,而这题中会有重复的元素。这题也是一道经典的回溯算法试题,在回溯算法中我们也会介绍,在这一章节我们使用位运算来解决。因为有重复的元素,如果还使用子集中的答案,就会出现重复的结果,所以这题的关键点就是怎么过滤掉重复的结果。
假设一个数组中出现了两个重复的元素,比如 [1,2,3,4,1] ,为了区分这两个 1 ,我把第二个 1 用 one 来表示,也就是 [1,2,3,4,one] 。假如我们选择了第一个 1 没有选择第二个 1 ,也就是 1 与 [2,3,4] 的组合,在假如我们选择了第二个 one 没有选第一个 1 ,也就是 one 与 [2,3,4] 的组合,我们看到这两种组合的结果是完全一样的,出现了重复,这个时候我们就需要去掉其中的一个组合。也就是说出现了重复的数字,如果一个选择一个没选择就会出现重复的结果。
对于这道题来说我们首先需要对数组进行排序,这样排序之后相同的数字就会挨着,当我们选择当前数字的时候如果当前数字和前一个数字相同,并且前一个没有选择我们就跳过。比如上面数组排序之后是 [1,one,2,3,4] ,这两个 1 可以都选也可以都不选,如果只选其中的一个就出现了 1 与 [2,3,4] 的组合和 one 与 [2,3,4] 的组合,我们把第一种过滤掉。
解题思路还是参照子集,来看下这题的代码。
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();// 返回的结果集合
Arrays.sort(nums);// 先对数组排序。
int length = nums.length;// 数组长度
int size = 1 << length;// 子集的个数(包含重复的,计算的时候会过滤掉重复的)。
for (int i = 0; i < size; i++) {
List<Integer> mList = new ArrayList<>();
boolean skip = false;// 有重复的跳过。
for (int j = 0; j < length; j++) {
if ((i & (1 << j)) == 0)// 数字 i 的第 j 位如果是 0 ,则跳过。
continue;
// 如果当前数字与上一个数相同,且没有选择上一个数,则可以跳过当前生成的子集。
if (j > 0 && nums[j] == nums[j - 1] && (i & (1 << (j - 1))) == 0) {
skip = true;// 遇到重复的跳过。
break;
}
mList.add(nums[j]);
}
if (!skip)// 如果没有重复的,就添加到集合res中。
res.add(mList);
}
return res;
}