78.子集
- 标签:递归、回溯
- 难度:7.5
求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树,不像组合问题只需要收集叶子结点。
递归参数要加上startIndex,因为取过的元素不能再取了。
递归终止条件就是startIndex大于数组的长度,剩余集合就为空了。
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums,0);
return res;
}
public void backtracking(int[] nums, int startIndex){
res.add(new ArrayList<>(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex; i < nums.length; i++){
path.add(nums[i]);
backtracking(nums,i+1);
path.remove(path.size() - 1);
}
}
90.子集II
- 标签:递归、回溯
- 难度:8.0
这道题在上一道的基础上加了去重的逻辑。
本题允许在某个path里有重复元素,但不允许出现重复的path,这就是允许“树枝重复”,但不允许同一“树层重复”。
用一个和nums等长的used数组,标记前一个元素是否加进path里。如果遍历到nums里的第i个元素,发现和第i-1个元素相等,且:
- used[i]==1,说明前一个元素是在同一个树枝里,那没事,可以重复。
- used[i]==0,说明前一个元素是在同一个树层里,那不行,直接continue。
为什么used[i]==0就一定在同一个树层里呢?根据下面的图,同一树层左边的元素一开始确实被加进了path里,used[i]也被置为1了,但是在回溯的过程中被恢复为了0。
所以回溯的时候一定记得,不仅path数组的最后一个元素要弹出,used数组的对应元素也要恢复。
注意:子集问题一定要排序。
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
used = new boolean[nums.length];
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int startIndex){
res.add(new ArrayList<>(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex; i < nums.length; i++){
if(i>0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
path.add(nums[i]);
used[i] = true;
backTracking(nums, i+1);
path.remove(path.size() - 1);
used[i] = false;
}
}