想要精通算法和SQL的成长之路 - 子集问题
前言
子集问题和 全排列 问题很像,只不过:
- 全排列问题的结果集元素个数和给定的数组元素个数是一样的。
- 子集问题的结果集元素个数则是 <= 给定数组元素个数。
上面这个不同的本质是啥呢?
- 全排列问题在进行遍历的时候,每一层都是从头开始遍历。
- 子集问题在进行遍历的时候,每一层的遍历位置依赖于上一层。
废话不多说,我们先来看下最基础的子集问题。
一. 子集
原题链接
给你一个整数数组 nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
- 输入:nums = [1,2,3]
- 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
对于该题目,回溯需要考虑的几个点:
- 递归终止的条件判断:数组遍历完毕。
- 递归要做的事情:把当前遍历到的数字放到临时集合中,并将其加入到结果集中。作为一个新的子集。
- 由于数组元素不会有重复,因此子集不用考虑去重操作。
1.1 递归终止判断
if(index >= nums.length){
return;
}
其实这里可以不用加,因为当满足index >= nums.length
的时候,for
循环也就停止了。
我们加入递归终止判断的目的也就是为了提前终止for
循环。因此在这里没有必要写。’
1.2 递归要做的事情
// res是最终的结果集。tmp是临时结果集,就是遍历过程中得到的子集
res.add(new ArrayList<>(tmp));
// index则是通过函数传入的,每层开始的下标依赖于上层传入的参数
for (int i = index; i < nums.length; i++) {
tmp.add(nums[i]);
backtrack(i + 1, nums, tmp);
// 回溯
tmp.remove(tmp.size() - 1);
}
最终结果:
public class Test78 {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtrack(0, nums, new ArrayList<>());
return res;
}
public void backtrack(int index, int[] nums, ArrayList<Integer> tmp) {
// 加入到结果集,空集也算(第一次遍历的时候)
res.add(new ArrayList<>(tmp));
for (int i = index; i < nums.length; i++) {
// 标准的:1.加入集合中。2.下一层递归。3.回溯(从集合中剔除)
tmp.add(nums[i]);
backtrack(i + 1, nums, tmp);
// 回溯
tmp.remove(tmp.size() - 1);
}
}
}
二. 子集II
在第一个子集问题的基础上,多了一个条件就是:
- 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)
那么就是在问题1的基础上,我们多一个子集的去重操作喽。
去重,我们就要判断,在添加子集的时候,遍历的前后两个元素是否有相同的元素,有的话跳过,也就是剪枝。
然后数组回溯里面的剪枝要记住一点:剪枝的前提就是数组要有序。 那么我们在原本代码基础上,会多这么一行代码:
Arrays.sort(nums);
在for
循环的时候,进行剪枝:
if (i > index && nums[i - 1] == nums[i]) {
continue;
}
最终结果如下:
public class Test90 {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 剪枝的前提,有序
Arrays.sort(nums);
backtrack(nums, 0, new ArrayList<>());
return res;
}
public void backtrack(int[] nums, int index, List<Integer> tmp) {
res.add(new ArrayList<>(tmp));
for (int i = index; i < nums.length; i++) {
// 跳过当前树层使用过的、相同的元素
if (i > index && nums[i - 1] == nums[i]) {
continue;
}
tmp.add(nums[i]);
backtrack(nums, i + 1, tmp);
tmp.remove(tmp.size() - 1);
}
}
}