78. 子集
题目描述:
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集
解答:
子集问题其实和组合问题很相似,不同在于子集问题需要在每次取数后都存入结果集
也就是组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
相应的,需要考虑次序时那就需要从0开始了(比如排列问题)。
此外子集问题终止条件有所不同,子集问题在搜索到超过数组边界时即为终止
需要额外注意的是空集也需要加入到结果集中去。
代码实现:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backTrace(vector<int>& nums, int start){
//超过边界 终止
if (start >= nums.size()){
return;
}
for (int i = start; i < nums.size(); i++){
path.push_back(nums[i]);
//每次都需要放入结果集
result.push_back(path);
backTrace(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
//放入空集
result.push_back(path);
backTrace(nums, 0);
return result;
}
};
90. 子集 II
题目描述:
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
解答:
这道题和上一题的不同在于nums数组中包含重复元素,这样一来和 40. 组合总和 II 可以说是十分相似了(见39. 组合总和、40. 组合总和 II_清榎的博客-CSDN博客)
那其实只需要在上一题的基础上先排序,再搜索整棵树,进行去重也就可以了。
两种方法,一种使用 used数组,另一种使用i和start的大小。
此处使用第二种办法,只有在树中同一层的元素才会出现i>start的情况,所以i > start && nums[i] == nums[i - 1]时进行去重。
代码实现:
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backTrace(vector<int>& nums, int start){
if (start >= nums.size())
return;
for (int i = start; i < nums.size(); i++){
if (i > start && nums[i] == nums[i - 1])
continue;
path.push_back(nums[i]);
result.push_back(path);
backTrace(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
result.push_back(path);
backTrace(nums, 0);
return result;
}
};
491. 递增子序列
题目描述:
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
解答:
这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。
这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的90.子集。
就是因为太像了,更要注意差别所在,要不就掉坑里了!
在90.子集中我们是通过排序,再加一个标记数组或者i>start来达到去重的目的。
而本题求自增子序列,是不能对原数组经行排序的,排完序的数组都是自增子序列了。
所以不能使用之前的去重逻辑!
如[4, 7, 6, 7]:
因为本题稍有复杂,考虑回溯三要素:
(1)参数及返回值:
vector<int>path;
vector<vector<int>>result;
void backTrace(vector<int>& nums, int start){}
(2)终止条件:
本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和90.子集一样。也可以不加终止条件,start每次都会加1,并不会无限递归。
(3)内部处理逻辑:
先进行去重,如果下个元素大于path中最后一个元素或者同一层中已经使用过就忽略。有无使用过采用哈希表进行标记。
内部处理时哈希表也需要相应进行修改。
代码实现:
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backTrace(vector<int>& nums, int start){
if (start >= nums.size())
return;
unordered_set<int> uset; // 使用set对本层元素进行去重
for (int i = start; i < nums.size(); i++){
//去重
if ((!path.empty() && nums[i] < path.back())|| uset.find(nums[i]) != uset.end())
continue;
path.push_back(nums[i]);
uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
if (path.size() >= 2)
result.push_back(path);
backTrace(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backTrace(nums, 0);
return result;
}
};