【玩转回溯算法专题】90. 子集 II 【中等】
1、力扣链接
https://leetcode.cn/problems/subsets-ii/submissions/552551584/
2、题目描述
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的
子集
(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
3、题目分析
回溯三部曲
1、递归函数参数
首先是题目给的nums[],for循环的起始值startIndex
2、递归终止条件
涉及去重,首先对nums数组排序,并定义一个used[]数组,用来记录nums[]的元素是否被使用过,也就是以第二个 2开始 作为第一个元素去进入for循环逻辑,是无用的,因为会重复
//当i>0 并且 两个相邻元素相等比如 【1 2 2】中 的2 2 现在遍历到第二个 2 时,且当前元素该轮次没有被使用过
if(i > 0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
3、单层搜索逻辑
//将元素加入path
path.add(nums[i]);
//该元素正在被使用
used[i] = true;
backing(nums,i+1);
path.removeLast();
used[i] = false;
1、Java
class Solution {
//回溯三部曲
//1、确定返回值
//2、确定终止条件
//3、单层for循环处理逻辑
List<List<Integer>> res = new ArrayList();
List<Integer> path = new ArrayList();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
if(nums.length == 0){
res.add(path);
return res;
}
Arrays.sort(nums);
//重复元素
used = new boolean[nums.length];
backing(nums,0);
return res;
}
public void backing(int[] nums,int startIndex){
res.add(new ArrayList(path));
if(startIndex >= nums.length){
return;
}
for(int i=startIndex;i<=nums.length-1;i++){
if(i > 0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
path.add(nums[i]);
used[i] = true;
backing(nums,i+1);
path.removeLast();
used[i] = false;
}
}
}
2、C++
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path);
for (int i = startIndex; i < nums.size(); i++) {
// 而我们要对同一树层使用过的元素进行跳过
if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
continue;
}
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0);
return result;
}
};