回溯-子集
组合是无序的,满足方案要求即可,对应不同的题意,有时候元素可以重复,有时候不能重复。
排列是有序的,同一批元素可以有多种排列。
子集跟上面两种又不同,首先空集是子集,一个元素也是子集,数组本身也是子集的子集。
所以子集的求解更复杂。
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
我们要解决以下问题
1.对应子集,怎样是可行解?
答案是对应数组中的元素,有或者没有,都是一个可行解
2.什么情况下递归结束?
因为不确定子集中的元素,所以我们需要自己定义一个指针cur,每进入一次新的递归,cur向右移动一位,当cur到达数组末尾的时候就结束递归。
class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { //定义一个指针cur,从前往后移动,cur==length,递归结束 dfs(nums, 0); return res; } private void dfs(int[] nums, int cur) { res.add(new ArrayList<>(path)); //递归到底 if (cur == nums.length) { return; } //取当前位置的元素 for(int i=cur;i<nums.length;i++){ path.add(nums[i]); dfs(nums, i + 1); //不取当前位置的元素 path.remove(path.size() - 1); } } }
案例2:
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
本题目是案例1的进阶版,难点在于数组中有重复元素,子集中不能有重复子集。
这是一个典型的回溯算法,非常经典,因为它基本上用到了回溯需要的所有手段
一、回溯的跳出
大家知道,回溯就是递归版的穷举,这个不难,关键点在于递归何时退出?
我们知道这个题目是要所有子集,对于结果并没有元素的长度限制。
因此我们只能自己设置一个指针,每得到一个元素,指针右移一位,当指针指到最后一个元素时,本轮递归就到底了!
二、子集肯定是不能重复的
因为是子集,所以一个元素不能使用两次,不重复使用同一个元素,要求我们定义一个used数组,上层递归中使用过的元素,下层的递归不允许再用!
三、不能有重复的子集
前面两步估计大部分人都能搞定,难在第三点,数组中有重复元素,如果仅仅简单的穷举,生成的子集一定会重复。
但题目要求不能有重复子集。这个就要求我们在合适的地方进行剪枝,关键是在哪里减呢?
如果有一个元素跟它前面的元素相等,比如[1,2(1),2(2)],
当遍历到2(2)时,发现前面的2(1)还没用过,那么这个地方就该剪掉!
原因是什么?
本轮遍历的后面的2(2)了,前面的2(1)还没有用过,说明在上一轮循环中已经出现过一次[1,2(1)]了
所以这里如果不剪枝,肯定会跟前面的重复。
class Solution { List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> subsetsWithDup(int[] nums) { Arrays.sort(nums); List<Integer> path = new ArrayList<>(); int n = nums.length; boolean[] used = new boolean[n]; dfs(nums, path, used, 0); return res; } private void dfs(int[] nums, List<Integer> path, boolean[] used, int cur) { res.add(new ArrayList<>(path)); if (cur == nums.length) { return; } for (int i = cur; i < nums.length; i++) { if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } if (!used[i]) { path.add(nums[i]); used[i] = true; dfs(nums, path, used, i + 1); path.remove(path.size() - 1); used[i] = false; } } } }