题目描述
给定一个整数数组,可能包含重复元素。请返回它的所有子集(幂集)。
注意;答案中不能包含相同子集。
样例
输入:[1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
算法
(暴力枚举) $O(n2^n)$
为了方便处理,我们先将数组排序,这样相同元素就会排在一起。
然后暴力搜索所有方案,搜索顺序是这样的:
我们先枚举每个不同的数,枚举到数 $x$ 时,我们再求出 $x$ 的个数 $k$,然后我们枚举在集合中放入 $0, 1, 2, …k $ 个 $x$,共 $k + 1$ 种情况。
当枚举完最后一个数时,表示我们已经选定了一个集合,将该集合加入答案中即可。
时间复杂度分析:不同子集的个数最多有 $2^n$ 个,另外存储答案时还需要 $O(n)$ 的计算量,所以时间复杂度是 $O(n2^n)$。
C++ 代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
dfs(0, nums);
return ans;
}
void dfs(int u, vector<int>&nums)
{
if (u == nums.size())
{
ans.push_back(path);
return;
}
int k = u;
while (k < nums.size() && nums[k] == nums[u]) k ++ ;
dfs(k, nums);
for (int i = u; i < k; i ++ )
{
path.push_back(nums[i]);
dfs(k, nums);
}
path.erase(path.end() - (k - u), path.end());
}
};
感觉这么写会更好懂一点
excellent
very excellent
y总在19暑期打卡的写法
这种算法的时间复杂度多少呀?
dfs里面那个主循环没看懂。。。为什么先dfs再push_back呢
i = 0 和 i = k又应该怎么更进一步地理解……
我明白了!谢谢你的注释
没太理解后面恢复现场的操作,请问erase掉的元素是相同的几个元素吗?for循环里面不是已经考虑过一个都不选的情况了吗,为啥还要erase?
没懂为甚么这样做
erase是去重吗
这里是回溯里的恢复现场操作,递归结束后需要将path数组恢复原状 。
明白了,大佬牛逼
y总牛逼,有个语法小问题,当nums为空时 ,dfs会执行一个ans.push_back(path) 操作,虽然path为空vector,但是此时ans当长度为1 不为空了,按理我们应该返回一个长度为0的ans才对啊
空集也算一个集合,这道题目的样例中给出了返回空集的情况。
对的对的,我漏看了,y总牛逼!
看不懂....,还是用set去重吧,智商不够 :-(
也是可以的,不过运行效率稍微低一些。
上面的算法简单来说,就是先枚举所有不同的数,然后再枚举每个数可以选多少个。
存储答案时还需要 O(n) 的计算量是指每次把path中的元素拷贝到ans中的计算量吗?
是的。在C++中
push_back
函数会把数组中的元素都复制一遍,需要 $O(n)$ 的计算量。我们也可以避免掉这里的计算量,方法是每次直接在
ans
中维护方案。计算直接从ans中维护方案,每个path的复杂度也是O(n)吧,最终的ans中数字个数就是2^n*n的量级,应该没法避免
应该是O(n2^n)? (毕竟78这道弱的版本是O(n2^n)了)
没错,已改。
另外需要注意一下这两道题目算法的区别,如果不要求记录方案,则弱化版由于是用二进制来做的,两重循环,时间复杂度仍是 $O(n2^n)$;但这道题目是用递归做的,总共递归到的节点个数是 $2 \times 2^n - 1$个,所以时间复杂度是 $O(2^n)$。