「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
前言
力扣第九十题 子集 II 如下所示:
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入: nums = [1,2,2]
输出: [[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入: nums = [0]
输出: [[],[0]]
一、思路
这一题需要注意的有两点:
- 数组
nums中可能有重复的元素 - 子集中的结果不能重复
这一题毋庸置疑是要使用递归来实现的,但是为了让我们在处理重复元素时更方便,我们第一步先将 nums 按照升序排列。
这样在递归的过程中,如果碰到 nums[i] 和 nums[i-1] 时我们跳过即可。要做到在子集中的结果不重复,我们只需要保证一点即可:同一层递归的处理中,保证每一次取的元素都是不同的
需要注意的是,当
nums = [1, 2, 2]时,我们肯定有一层递归是为了取子集大小为2的结果。
当我们第一个元素取得nums[1]值为2时,下一层递归会从nums[2]开始取。
虽然此时nums[2] == nums[1]当时因为我们在当前层中是第一次取2,所以对于[2, 2]这个结果我们不能跳过
为了保证当前层递归中取的值都是不同的,我们可以采取以下两个措施
- 使用布尔数组
visited[]表示当前层的第i个元素是否访问过 - 下一次递归取元素的起始位置为
i+1
综上所述,大致的步骤如下所示:
- 因知道递归取的子集长度的范围为
0 ~ len,所以我们可以在主函数中枚举1 ~ len-1长度。(0一定为空集,len一定为全集) - 递归的方法就是获取所有长度为
x (0 < x < len)的子集(这样在添加结果时比较方便,只要当前list.size = x即可)
tips:在返回结果集前,需要加上空集和全集,因为我们在主函数中是从
1 ~ len-1的。
二、实现
实现代码
实现代码与思路中保持一直
List<List<Integer>> ret = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 将nums升序排列
Arrays.sort(nums);
int len = nums.length;
ret.add(new ArrayList<>());
for (int i=1; i<len; i++) {
dfs(nums, 0, i, new boolean[len], new ArrayList<>());
}
ret.add(Arrays.stream(nums).boxed().collect(Collectors.toList()));
return ret;
}
public void dfs(int[] nums, int begin, int len, boolean[] visited, List<Integer> list) {
if(list.size() == len) {
ret.add(new ArrayList<>(list));
return;
}
for (int i=begin; i<nums.length; i++) {
// 剪枝
if (i > 0 && nums[i] == nums[i-1] && !visited[i-1])
continue;
list.add(nums[i]);
visited[i] = true;
dfs(nums, i+1, len, visited, list);
visited[i] = false;
list.remove(list.size()-1);
}
}
测试代码
public static void main(String[] args) {
int[] nums = {1,2,2, 2};
new Number90().subsetsWithDup(nums);
}
结果
三、总结
这个击败率还不是很理想,仍有优化的空间。
感谢看到最后,非常荣幸能够帮助到你~♥
如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~