题目链接https://leetcode-cn.com/problems/subsets-ii/
- 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 示例 2:
输入:nums = [0] 输出:[[],[0]]
对于第78题的子集I
, 我们可以用回溯法直接来解:
public List<List<Integer>> subsets(int[] nums) {
backTrace(nums,0,new ArrayList<Integer>());
return res;
}
List<List<Integer>> res = new ArrayList<List<Integer>>();
public void backTrace(int[] nums , int idx, List<Integer> list_now){
res.add(new ArrayList<Integer>(list_now));
for(int i = idx ; i < nums.length ; i++){
list_now.add(nums[i]);
backTrace(nums,i+1,list_now);
list_now.remove((Integer)nums[i]);
}
}
但是这一题不同的是,题目给出的数组中会有重复元素,如果继续使用子集I的方法,最终的答案里会有重复的子集,例如:
nums = [1,2,2,3]
在list_now = [ 1 ] 时 ,我们先加入nums中下标为1的元素“2”,继续往下递归可以得到结果[1,2],[1,2,2],[1,2,2,3],[1,2,3];然后回溯到 list_now = [ 1 ] 时,再次加入下标为2的元素“2” ,继续往下递归,又会得到[1,2],[1,2,3];这样结果集中就会有重复的子集;因此,我们需要想办法,在回溯过程中跳过某些重复子集;
我们画出[1,2,2,3]在回溯时的递归树:
标红的节点,就是多余的节点,应该要被舍弃;
可以看出被标红的节点有一个特性,就是遍历到该节点处时,新加入的元素,与其在nums中前一个位置的元素相同;因此,我们可以加入一个判断:如果当前所需要加入的元素nums[i] == nums[i-1] ,就不往下继续回溯,而是跳过该节点:
public void backTrace(int[] nums , int idx, List<Integer> list_now){
res.add(new ArrayList<Integer>(list_now));
for(int i = idx ; i < nums.length ; i++){
//如果nums[i]==nums[i-1],就跳过这一个节点;
if(i>idx && nums[i]==nums[i-1]){continue;}
else
{
list_now.add(nums[i]);
backTrace(nums,i+1,list_now);
list_now.remove((Integer)nums[i]);
}
}
}
上面的代码中,是否跳过这一节点的判断还加了一个 i>idx ,这是因为对于每个for循环来说,backTrace(nums,i+1,list_now)等于是进入了递归树的下一层,例如上图中的这个节点:
对于节点[1]来说,我们需要舍弃的,不是阻止他进入递归树的下一层,而是阻止递归树的下一层中出现相同的节点;也可以这么理解:如果当前正要加入List_now的元素在这个for循环阶段没有加入过,我们才进行加入,那么第一个元素肯定是要加入的;当[1]准备加入第二个2元素时,由于2在当前for循环中加入过了,于是选择跳过;
而对于[1,2]这个节点来说,它在加入nums[2] = 2这个元素时,虽然num[2]==nums[1];但是在当前for循环中并没有加过2,于是还是需要加入,否则树在这里就直接断了。