思路一:回溯、去重
class Solution {
//设置临时集合
List<Integer> temp = new ArrayList<Integer>();
//设置子集总集合
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
//将数组进行一次排序,用来避免重复收集的问题(排完序之后可以使用收集的方式/Set的方式来进行去重)
Arrays.sort(nums);
//数组的长度用来构造visted[]数组
int n = nums.length;
//使用 visited 数组来记录哪一个元素在当前路径中被使用了
boolean[] visited = new boolean[n];
//从0的位置开始递归搜索nums数组,因此参数是nums和0
backtrace(nums,0,visited);
//返回总集合
return ans;
}
//采用回溯来求解本题
public void backtrace(int[] nums,int start,boolean visited[]){
//将temp加入ans结果集合中
ans.add(new ArrayList<Integer>(temp));
//用for循环遍历数组(背后实际上已经模拟成一颗树,也就是用for循环来模拟遍历树)收集结果
for(int i = start;i < nums.length; ++i){
//如果当前元素和前一个元素相同,而且前一个元素没有被访问,说明前一个相同的元素在当前层已经被用过了
if(i > 0 && nums[i - 1] == nums[i] && !visited[i - 1]) continue;
// 记录下来,用过了当前的元素
visited[i] = true;
// 放到临时集合中
temp.add(nums[i]);
//递归
backtrace(nums,i+1,visited);
//回溯,将当前标记为已经访问的结点取消标记,这样回到上一层就会使一个没有被访问的状态
visited[i] = false;
temp.remove(temp.size() - 1);
}
}
}
思路二:Set去重(时间和空间复杂度都高)
class Solution {
//所有子集的集合
Set<List<Integer>> subsets = new HashSet<>();
//临时集合,用来存放单一子集
List<Integer> temp = new ArrayList<Integer>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
int length = nums.length;
//用一个循环来列举出一个集合所有子集的二进制表示 例如[A,B,C] 的子集二进制表示[[000][001][010][011][100][101][110][111]]
for(int i = 0; i<(1<<length); i++){
//清空临时集合
temp.clear();
for(int j = 0; j < length; j++){
//判断临时集合中的每一位是否在[A,B,C]中,如果在,将该位add进临时集合中
if((i&(1<<j))!=0){
temp.add(nums[j]);
}
}
//把每一个临时集合add进总集合中
subsets.add(new ArrayList<>(temp));
}
return new ArrayList<>(subsets);
}
}