注:
题目:
给你一个整数数组 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
题解:
思路
做本题之前一定要先做78.子集 (opens new window)。
这道题目和leetcode 78的区别就是集合里有重复元素了,而且求取的子集要去重。
那么关于回溯算法中的去重问题,在40.组合总和II (opens new window)中已经详细讲解过了,和本题是一个套路。
剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解树层去重和树枝去重非常重要。
用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
复杂度分析
时间复杂度:O(n×2n),其中 n 是数组 nums 的长度。排序的时间复杂度为 O(nlogn)。最坏情况下 nums 中无重复元素,需要枚举其所有 2n 个子集,每个子集加入答案时需要拷贝一份,耗时 O(n),一共需要 O(n×2n)+O(n)=O(n×2n) 的时间来构造子集。由于在渐进意义上 O(nlogn) 小于 O(n×2n),故总的时间复杂度为 O(n×2n)。
空间复杂度:O(n)。临时数组 t 的空间代价是 O(n),递归时栈空间的代价为 O(n)。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums,vector<int>& visited,int index ){
if(index>nums.size()){
return ;
}
for(int i=index;i<nums.size();i++){
// visited[i - 1] == 1,说明同一树枝candidates[i - 1]使用过
// visited[i - 1] == 0,说明同一树层candidates[i - 1]使用过
if(i>=1&&nums[i-1]==nums[i]&&visited[i-1]==0){
continue;
}
visited[i]=1;
path.push_back(nums[i]);
result.push_back(path);
backtracking(nums,visited,i+1);
path.pop_back();
visited[i]=0;
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<int> visited(nums.size(),0);
sort(nums.begin(),nums.end());
result.push_back(path);
backtracking(nums,visited,0);
return result;
}
};