简介
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
- 时间复杂度:O(2^n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2^n)
 空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n),每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为O(n)
78.子集
题目描述
解题思路
递归函数参数
res,path,因为不重复选取所以需要startIndex
- 递归终止条件
 
那么什么时候剩余集合为空呢?就是startIndex已经大于数组的长度了,就终止了,因为没有元素 可取(加不加都可以,for中已经判断了)
- 单层搜索逻辑
 
求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。
public List<List<Integer>> subsets(int[] nums) {if (nums.length == 0) {return res;}backtracking(nums, 0);return res;}List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public void backtracking(int[] nums, int startIndex) {// 收集子集,要放在终止添加的上面,否则会漏掉自己res.add(new ArrayList<>(path)); // 此时时每层递归都要将路径加入结果集,因为求得时子集if (startIndex >= nums.length) { // 可以不同加终止条件,后面for也满足return;}for (int i = startIndex; i < nums.length; i++) {path.add(nums[i]);backtracking(nums, i + 1);path.removeLast(); // 回溯}}
90.子集II
题目描述
解题思路
注意题目给的是包含重复元素,但结果不能包含重复元素,所以可以使用树层去重,本题和78.子集相差不大。(注意树层去重需要排序)
public List<List<Integer>> subsetsWithDup(int[] nums) {if (nums.length == 0) {return res;}Arrays.sort(nums); // 必须要进行排序,才能达到去重效果backtracking(nums, 0, new boolean[nums.length]);return res;}List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public void backtracking(int[] nums, int startIndex, boolean[] used) {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] == false) { // 已经被选择continue;} else { // 注意不能放在if里面,以为if里面将 i > 0 之后才会进行判断path.add(nums[i]);used[i] = true;backtracking(nums, i + 1, used);used[i] = false;path.removeLast();}}}
树层去重还可以使用哈希表
可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?
public List<List<Integer>> subsetsWithDup(int[] nums) {Arrays.sort(nums);// backtracking(nums, new boolean[nums.length], 0);// set做法backtracking(nums, 0);return res;}List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();/*** 使用set** @param nums* @param startIndex*/public void backtracking(int[] nums, int startIndex) {res.add(new ArrayList<>(path));if (startIndex >= nums.length) {return;}Set<Integer> set = new HashSet<>();for (int i = startIndex; i < nums.length; i++) {if (!set.isEmpty() && set.contains(nums[i])) continue;set.add(nums[i]);path.add(nums[i]);backtracking(nums, i + 1);path.removeLast();}}
491.递增子序列
题目描述
解题思路
在90.子集II中我们是通过排序,再加一个标记数组来达到去重的目的。
而本题求自增子序列,是不能对原数组经行排序的,排完序的数组都是自增子序列了。
所以不能使用之前的去重逻辑!
- 递归函数参数
 
不能重复选取,使用startIndex
- 终止条件
 
本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和回溯算法:求子集问题!一        样,可以不加终止条件,startIndex每次都会加1,并不会无限递归。
但本题收集结果有所不同,题目要求递增子序列大小至少为2。
- 单层搜索逻辑
 
在图中可以看出,同一父节点下的同层上使用过的元素就不能在使用了
使用map表示哈希表
public List<List<Integer>> findSubsequences(int[] nums) {if (nums.length == 0) {return res;}backtracking(nums, 0);return res;}List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();/*** 使用hash法来进行判重** @param nums* @param startIndex*/public void backtracking(int[] nums, int startIndex) {if (path.size() > 1) {res.add(new ArrayList<>(path)); // 取树枝上的值}Map<Integer, Integer> map = new HashMap<>(); // 用来判断本层是否重复使用,每次for之前,也就是每层都需要重新使用一个map来判断for (int i = startIndex; i < nums.length; i++) {if (!path.isEmpty() && nums[i] < path.getLast()) { // 如果2个数不是递增,跳过此次选择continue;}if (map.getOrDefault(nums[i], 0) > 0) { // 本层中已经取过此元素continue;}map.put(nums[i], map.getOrDefault(nums[i], 0) + 1); // 去过就放入map中path.add(nums[i]);backtracking(nums, i + 1);path.removeLast(); // 回溯}}
使用数组来表示哈希表,节约空间
public List<List<Integer>> findSubsequences(int[] nums) {backtracking(nums, 0);return res;}List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public void backtracking(int[] nums, int startIndex) {// 大小至少为2才可以算作一个递增子序列if (path.size() > 1) {res.add(new ArrayList<>(path));}if (startIndex >= nums.length) {return;}// 使用数组来表示哈希表// 由于题目给的数是在[-100,100]之间,所以申请201个单元int[] used = new int[201];for (int i = startIndex; i < nums.length; i++) {// 不是递增序列或者同一层出现过,直接跳过此次选取// 注意是path的最后一个元素进行比较if ((!path.isEmpty() && nums[i] < path.getLast()) || used[nums[i] + 100] == 1) continue;path.add(nums[i]);used[nums[i] + 100] = 1;backtracking(nums, i + 1);// 错误操作:此时的哈希表表只代表本层是否使用过,和下一层无关// 所以下一次回溯时,和本层是否选取无关系,所以不能回溯// 注意和前面排序之后使用used区分// used时整个所有数都是用used来判断,所以有关系// used[nums[i] + 100] = 0;path.removeLast();}}
