给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
(1)遍历添加法,参考子集;
思路:每一个新子集都是当前已有子集加上当前遍历数字即可,因为有重复所以先对数组排序,并且对链表添加进行去重处理;
弊端:每一次都要判断是否重复,很费时间;
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
//建立结果集并且添加空集
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());
//对数组进行排序
Arrays.sort(nums);
sub1(nums, res);
return res;
}
//遍历法
private void sub1(int[] nums, List<List<Integer>> res) {
//每次都在已有子集的基础上添加当前遍历的元素,然后生成新的子集
for (int n : nums) {
//记录当前的子集个数
int sumSub = res.size();
//拿出当前的子集个数,加上n生成新子集并加入
for (int i = 0; i < sumSub; i ++) {
//add()方法返回的Boolean值
List<Integer> sub = new ArrayList(res.get(i));//根据当前子集建立新的子集
sub.add(n);
//不能重复
if (!res.contains(sub))
res.add(sub);
}
}
}
}
(2)动态规划,对上面算法进行改进;
思路:其实就是对重复的处理;遍历分为两种情况:一,没有重复,那么就从结果集的所有子集加上该元素num[i],并且记录没添加以前的结果集子集个数;二,有重复,那么就在添加num[i-1]后的个数减去没添加以前的个数,也就添加num[i-1]后形成的这些新子集上面添加这个重复元素即可,并且记录没添加当前这个数之前的结果集子集个数,再添加完之后更新记录;
注意:说起来比较绕口,看一下代码会明白滴!
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
//建立结果集并且添加空集
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());
//对数组进行排序
Arrays.sort(nums);
sub(nums, res);
return res;
}
//每次都要判断是否已经存在,耗时间
private void sub(int[] nums, List<List<Integer>> res) {
if (nums == null || nums.length == 0)
return;
res.add(Arrays.asList(nums[0]));
//记录每次遍历时res的子集个数(也就是没添加新子集时的长度)
int index = 1;
//没重复则全部添加子集然后生成新子集
for (int i = 1; i < nums.length; i ++) {
if (nums[i] != nums[i-1]) {
//记录当前的子集个数(更新)
index = res.size();
//拿出当前的子集个数,加上n生成新子集并加入
for (int j = 0; j < index; j ++) {
//add()方法返回的Boolean值
List<Integer> sub = new ArrayList(res.get(j));//根据当前子集建立新的子集
sub.add(nums[i]);
res.add(sub);
}
} else {//相等则从遍历nums[i-1]位置开始遍历res然后生成新的子集
int temp = res.size();
for (int j = index; j < temp; j ++) {
List<Integer> sub = new ArrayList(res.get(j));//根据当前子集建立新的子集
sub.add(nums[i]);
res.add(sub);
}
//更新
index = temp;
}
}
}
}
(3)递归回溯
思路:就是一句话,递归时挨个添加,回溯时看这个元素与其前一个元素是否相等;
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
//建立结果集并且添加空集
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());
//对数组进行排序
Arrays.sort(nums);
subset(nums,0,new ArrayList<>(),res,new boolean[nums.length]);
return res;
//递归回溯
private void subset(int[] nums, int start, List<Integer> sub, List<List<Integer>> res, boolean[] v) {
//遍历数组,
for (int i = start; i < nums.length; i ++) {
if (!v[i] && (i == 0 || nums[i] != nums[i-1] || v[i-1])) {//要加上重复的判断(首次看最后条件,回溯看中间条件)
sub.add(nums[i]);//子集添加新元素
res.add(new ArrayList<>(sub));//生成新子集添加到结果集中
v[i] = true;//递归时挨个添加
subset(nums,i+1,sub,res,v);//递归
v[i] = false;//回溯
sub.remove(sub.size()-1);//回溯时删除子集最后一个元素
}
}
}
}