题目
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入: nums = [1,2,2]
输出: [[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入: nums = [0]
输出: [[],[0]]
提示:
1 <= nums.length <= 10-10 <= nums[i] <= 10
题解
解题思路
这道题使用迭代或者递归来解决都可以,主要需要实现枚举所有的子集,收集到不重复的子集集合。其中难点在于确保最终的结果中不包含重复的子集。
当数组中有多个相同的元素相邻时,如果一个子集中选择了这些元素中部分元素,并且不是选择靠前的几个,那么这个子集可以跳过不选,因为一定会有跟该子集相同的子集。举个例子,现在有 2,2,2,2 这几个相同的元素并且相邻,此时如果子集中选择了第一和第三的 2,那么最终会和选择第一和第二或者第二和第三等等的子集重复。所以为了防止重复,当要选择若干个相同的元素时,我们可以限定只选择靠前的若干个即可。
因此,落实到具体的做法,我们需要先对数组进行排序,使得相同的元素相邻,然后遍历各子集的每个元素时,都判断是否存在相同并且没有被选中的前一个元素,如果有,就直接跳过该子集。
那如何遍历数组所有的子集呢? 我们可以使用二进制数来表示各个子集,例如对于 3 位的数组,可以使用三位的二进制数表示其子集,例如 010,表示该子集选中第 2 个元素。这样一来,小于 (1 << 3) 的所有数即对应着数组的所有子集。
代码
class Solution {
List<Integer> cur = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 先对 nums 排序,确保相同的元素连续
Arrays.sort(nums);
// 数组长度
int n = nums.length;
for (int mask = 0; mask < (1 << n); mask++) {
// 清空 cur
cur.clear();
// 记录是否跳过当前子集
boolean flag = false;
// 遍历当前子集的元素,添加到 cur 中
for (int i = 0; i < n; i++) {
// 通过 mask 判断是否当前子集是否选中当前元素
if ((mask & (1 << i)) != 0) {
// 选中
// 前一个元素是否和当前元素相同 && 未被选中
if (i > 0 && ((mask & (1 << (i - 1))) == 0) && nums[i] == nums[i - 1]) {
flag = true;
break;
}
cur.add(nums[i]);
}
}
if (!flag) {
ans.add(new ArrayList(cur));
}
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n×2ⁿ)
其中 n 是数组的长度,一共需要枚举 2ⁿ 个子集,每个子集的遍历需要 O(n) 的时间复杂度。 - 空间复杂度:O(n)
使用了 cur 集合临时存储子集。
优质项目推荐
推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。
lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体
公众号
有兴趣可以关注公众号一起学习更多的干货哈!