题目链接: LC 90. 子集 II
题目:
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的
子集(幂集)。
要求 : 解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
提示
. 1 <= nums.length <= 10
. -10 <= nums[i] <= 10
基本思路分析
-
其实就是一个排列组合题目, 例如上面例子中的集合为 1, 2, 2, 其中1的个数为1, 2的个数为2, 故我们进行一个子集选择的时候,1的可能选的个数为0或者1, 一共2种可能, 2 可能选择的个数为 0 个, 1个,2个, 共有三种可能。 故总共的选择根据乘法原理为 2 * 3 = 6 一共六种选择,不过我们题目要求的不是子集的个数,而是子集的各种情况。
-
故通过上面的分析,我们知道我们需要保存下nums数组中每个元素的个数,即记录下每个数字的选择方案,是选择0个,还是选择1个,还是X个 。以上面的nums = [1,2,2] 为例子,其具体的过程我们可以用递归搜索树表示如下:
- 这是一一个典型的深度优先遍历(DFS), 实现的时候,我们用递归进行实现,注意的问题为现场恢复和递归终止条件的判断,看数据的范围数组最多为10个整数,且整数的值范围为 -10 ~ 10 , 故这里的话我们可以直接遍历所有值从 -10 ~ 10 的所有组合,因为如果nums中没有某个值的话,那么它就是0个并不会影响我们上面的递归搜索树, 故下面是具体的代码实现.
class Solution
{
public:
unordered_map<int, int> cnt; // 记录我们nums中的数字具体出现的次数. 其实这里用个普通的数组存储也没毛病.
vector<vector<int>> res; // 记录我们最后结果子集的所有情况
vector<int> path; // 记录我们每一个子集
void dfs(int u)
{
if(u > 10) res.push_back(path); // 若大于10,说明从-10到10所有情况都取了一遍,此时可以作为一个子集加入res中
else
{
for(int i = 0; i < cnt[u] + 1; ++i) // 遍历的第1遍,说明此时只有0个u在path中
{
dfs(u + 1); //第2遍才加入了一个u, 因为要等dfs(u + 1)递归完第一遍才
path.push_back(u);
}
for(int i = 0; i < cnt[u] + 1; ++i) path.pop_back(); // 现场恢复,每次遍历完一个分支要将现场恢复到上一个分支.结合上面的递归搜索树理解
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums)
{
for(auto val : nums) cnt[val]++; // 遍历一遍 nums, 记录其中每个数字出现的数字
dfs(-10); // 从-10开始遍历
return res;
}
};
其他dfs相关题目链接
AC AcWing 842. 排列数字
AC 93. 递归实现组合型枚举
AC 843. n-皇后问题