题意:
给出数组的子集(不能包含重复的元素)
思路:
1. 如果数组不包含重复元素,那么dfs的简单回溯,还是很简单的,类似于
res = []
tmp = []
dfs(cur, nums[]):
#不添加当前元素
dfs(cur+1)
#添加添加元素
tmp.append(nums[cur])
dfs(cur+1)
tmp.remove(nums[cur])
···
回溯的关键点,就是这里的remove,将现场清理干净
这样的话,回到上一层的结点时,tmp才是正确的状态
···
2. 可是这里的元素是可重复的,所以思路可以转换一下,将每个位置设置成(key:value)的形式,也就是每个位置的选择是 [0, value]
tmp到当前位置后,添加元素是从无到该值的最大数量
class Solution {
List<Integer> tmp;
HashMap<Integer, Integer> dict;
List<Integer> array;
List<List<Integer>> res;
void dfs(int cur, int n){
if(cur == n){
System.out.println(tmp);
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=0;i<=dict.get(array.get(cur));++i){
for(int j=1;j<=i;++j){
tmp.add(array.get(cur));
}
dfs(cur+1, n);
for(int j=1;j<=i;++j){
tmp.remove(tmp.size()-1);
}
}
}
public List<List<Integer>> subsetsWithDup(int[] nums) {
tmp = new ArrayList<Integer>();
dict = new HashMap<Integer, Integer>();
int n = nums.length;
for(int i=0;i<n;++i){
dict.put(nums[i] , dict.getOrDefault(nums[i], 0) + 1);
}
array = new ArrayList<Integer>();
for(Integer i : dict.keySet()){
array.add(i);
}
res = new ArrayList<List<Integer>>();
dfs(0, array.size());
return res;
}
}
3. 但是,还有更骚的写法
第一种方法不行的原因在于,同样的数字,选了后者,没有选择前面的某一个的话,必然导致重复。
迭代时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。
这个条件,给回溯算法施加了约束,对于连续相同的数字,如果前面选了,后面才可以选,前面不选的话,后面也不可以选。
对于[2, 2, 2]来讲,只会出现下面的情况(1代表选择, 0代表不选择)
[0, 0, 0]
[1, 0, 0]
[1, 1, 0]
[1, 1, 1]
所以,约束限制了不会出现重复元素的情况。
class Solution {
List<Integer> t = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
dfs(false, 0, nums);
return ans;
}
public void dfs(boolean choosePre, int cur, int[] nums) {
if (cur == nums.length) {
ans.add(new ArrayList<Integer>(t));
return;
}
dfs(false, cur + 1, nums);
if (!choosePre && cur > 0 && nums[cur - 1] == nums[cur]) {
return;
}
t.add(nums[cur]);
dfs(true, cur + 1, nums);
t.remove(t.size() - 1);
}
}