今日主要总结一下,90. 子集 II
题目:90. 子集 II
题目描述:
给你一个整数数组 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. 子集
和40. 组合总和 II这两道题,这道题其实就是以上两道题的结合应用
或者看一下一文搞懂回溯解决子集问题和一文搞懂回溯解决有重集合中结果去重的组合问题我对这两道题有详细的讲解
这道题目和题目78. 子集如下区别:
这道题目和78.子集的区别就是集合里有重复元素了,而且求取的子集要去重。
本题的难点在于:输入集合有重复元素,输出但还不能有重复的组合。
一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
所以要在搜索的过程中就去掉重复组合。
所谓去重,其实就是使用过的元素不能重复选取。 这么一说好像很简单!
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过”
是造成大家没有彻底理解去重的根本原因。那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
used[i - 1] == true,说明同一树枝nums[i - 1]使用过,不用去掉!
used[i - 1] == false,说明同一树层nums[i - 1]使用过,//就是要对同一树层使用过的元素进行跳过!
一、解法一(使用used数组)
C++代码
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<bool> used;
void backtracing(vector<int>& nums, int startIndex, vector<bool>& used){
res.push_back(path);
if(startIndex >= nums.size()) return;
for(int i = startIndex; i < nums.size(); i++){
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 而我们要对同一树层使用过的元素进行跳过
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] ==0) continue;
path.push_back(nums[i]);
used[i] = 1;
backtracing(nums, i + 1, used);
used[i] = 0;
path.pop_back();
}
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
res.clear();
path.clear();
used.resize(nums.size(), 0);
sort(nums.begin(), nums.end());
backtracing(nums, 0, used);
return res;
}
};
二、解法二(使用startIndex来去重)
C++代码
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracing(vector<int>& nums, int startIndex){
res.push_back(path);
if(startIndex >= nums.size()) return;
for(int i = startIndex; i < nums.size(); i++){
// 而我们要对同一树层使用过的元素进行跳过
// 这里使用i > startIndex,之前讲过for循环是在同一树层上进行广度横向遍历
// 所以当i > startIndex时剪掉的同一树层上需要剪枝的元素
// 而递归对应在同一树枝进行纵向深度遍历,所以在同一树枝上的i始终等于startIndex
if(i > startIndex && nums[i] == nums[i - 1]) continue;
path.push_back(nums[i]);
backtracing(nums, i + 1);
path.pop_back();
}
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
res.clear();
path.clear();
sort(nums.begin(), nums.end());
backtracing(nums, 0);
return res;
}
};
总结
1. 算法思想总结:输入数组nums有重复元素,输出但还不能有重复的组合这种问题一般方法无法解决时就可以考虑外加一个辅助数组used,用来记录元素是否之前使用过,去重的重任used来完成
也可以使用startIndex来去重,但是不具有通用性,有的问题还是需要使用used数组来解决
所以方法一必须要掌握,方法二有精力可以作为拓展拔高!
2. 关于使不使用startIndex的问题:
startIndex的核心作用是用于去重
因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而77. 组合和216.组合总和III都是是求同一个集合中的组合。
所以当从一个集合中取元素的时候就需要startIndex ,而从两个及以上元素中去元素就不需要startIndex了
近期复盘感悟分享:
为什么大多数优质青年都是单身呢?
答案就是读书能改变命运,
但不代表能改变你的桃花运,
世俗意义上的优质男女大多都是受过良好的教育,
但往往因为他们受过了这样良好的教育所以在找对象这方面,
他会陷入一个做题逻辑,
反反覆覆的对比自己的硬性条件和对方的硬性条件,
但他的内心又极度的渴望和自己喜欢的人在一起,
所以就会陷入一个非常拧巴的境地。
所以为什么有那么多高学历的人被PUA。
答案就是高学历,它是有代价的。
第一点,你花了足够的时间花在学习上,
而在人际关系上的博弈消耗的时间就会比较少。
第二,家庭教育为了让小孩子学习帮他们屏蔽了很多社交活动,
所以他们比较单纯。
第三也是最主要的高学历的副作用,
相信理念,
相信逻辑,
相信数据,
相信努力,
却唯独不相信自己的感受。
所以在不耽误学业和工作的前提下,
建议尽早在感情问题上进行一些思考和投入,
不然到了被催婚的年纪你可能会后悔!
无论最后结局怎么样,
都是一种成长!!!
别怀疑善良
是否能得到补偿
光阴如风如霜
倾心所想
璀璨星亮!!!