算法学习记录~2023.X.XX~章节DayX~题目号.题目标题 & 题目号.题目标题
90.子集II
题目链接
思路
这道题目和 78.子集 区别就是集合里有重复元素了,而且求取的子集要去重。
关于回溯算法中的去重问题,和 40.组合总和II 中的一样,使用一个used数组来进行本树层的去重。
去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了
代码1:使用used数组去重
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking (vector<int> nums, int startIndex, vector<bool> used){
result.push_back(path);
if (startIndex >= nums.size()){
return ;
}
for (int i = startIndex; i < nums.size(); i++){
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) //去重
continue;
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); //必须先排序
backtracking(nums, 0, used);
return result;
}
};
代码2:使用set去重
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking (vector<int> nums, int startIndex){
result.push_back(path);
unordered_set<int> set; //定义set对同一节点下的本树层去重。注意这个的位置不能在for循环里
if (startIndex >= nums.size()){
return ;
}
for (int i = startIndex; i < nums.size(); i++){
if (set.find(nums[i]) != set.end()) //去重,如果能找到则说明本层已经使用过,直接pass
continue;
path.push_back(nums[i]);
set.insert(nums[i]); //记录元素
backtracking(nums, i + 1);
path.pop_back();
//由于是每层单独定义的set因此并不需要erase掉set
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); //必须先排序
backtracking(nums, 0);
return result;
}
};
总结
491. 递增子序列
题目链接
思路
分为两个问题, 一个是判断递增子序列的问题,另一个是消除重复的子序列。

在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了。
因此在每一层的循环中,使用 unordered_set 来进行去重,同时因为是每一层定义的,因此不需要回溯。
unordered_set used 是记录本层元素是否重复使用
代码
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking (vector<int> nums, int startIndex){
if (path.size() > 1)
result.push_back(path); // 注意这里不要加return,要取树上的节点
unordered_set<int> used; //用于记录本层用没用过相同的数
for (int i = startIndex; i < nums.size(); i ++){ //因为最少两个数
//不再递增 或 当前树已经在这一层用过
if ((!path.empty() && nums[i] < path.back()) || used.find(nums[i]) != used.end())
continue;
used.insert(nums[i]); //用了就要标记一下
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
总结
本题使用了unordered_set去重。一开始自己想的时候看例子没能发现重复的问题,采用类似used去重的关键在于想清楚是要同一数层不能重复,这个在写代码时需要注意
46.全排列
题目链接
思路
思路其实和组合差不多,只不过由于每次都要从头到尾找,因此不需要startIndex了。
但排列问题需要一个used数组,标记已经选择的元素。
代码
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking (vector<int> nums, vector<bool> used){
if (path.size() == nums.size()){
result.push_back(path);
return ;
}
for (int i = 0; i < nums.size(); i++){
if (used[i] == true)
continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
总结
47.全排列 II
题目链接
思路
这道题目和 46.全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。也就是多了一个去重。
在40.组合总和II 、90.子集II 都涉及了组合问题和子集问题如何去重,放在排列思路也类似。
同样重要的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了

代码1:使用used去重
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking (vector<int> nums, vector<bool> used){
if (path.size() == nums.size()){
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++){
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
continue;
}
if (used[i] == false){ //注意这个判断false的条件
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
代码2:使用set去重
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
unordered_set<int> uset; // 控制某一节点下的同一层元素不能重复
for (int i = 0; i < nums.size(); i++) {
if (uset.find(nums[i]) != uset.end()) {
continue;
}
if (used[i] == false) {
uset.insert(nums[i]); // 记录元素
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 排序
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
总结
这题看起来和 46.全排列 一样都用了used,但是由于使用了排序,那么代码中的逻辑就可以保证本树层不会重复放入相同元素。
有关这种去重的问题还需要多练习和思考原理。
文章介绍了如何使用回溯算法解决子集II、递增子序列和全排列II等题目,重点讨论了在有重复元素的情况下如何通过used数组或set进行去重,确保结果的正确性。每个问题都提供了代码实现,包括使用used数组和set两种方法。

被折叠的 条评论
为什么被折叠?



