forked from youngyangyang04/leetcode-master
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
27f1706
commit c449518
Showing
6 changed files
with
280 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
|
||
|
||
> 在 [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈! | ||
接下来我再把这块再讲一下。 | ||
|
||
在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中的去重和 [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中的去重 都是 同一父节点下本层的去重。 | ||
|
||
[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢? | ||
|
||
我用没有排序的集合{2,1,2,2}来举例子画一个图,如图: | ||
|
||
 | ||
|
||
图中,大家就很明显的看到,子集重复了。 | ||
|
||
那么下面我针对[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) 给出使用set来对本层去重的代码实现。 | ||
|
||
# 90.子集II | ||
|
||
used数组去重版本: [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) | ||
|
||
使用set去重的版本如下: | ||
|
||
```C++ | ||
class Solution { | ||
private: | ||
vector<vector<int>> result; | ||
vector<int> path; | ||
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) { | ||
result.push_back(path); | ||
unordered_set<int> uset; // 定义set对同一节点下的本层去重 | ||
for (int i = startIndex; i < nums.size(); i++) { | ||
if (uset.find(nums[i]) != uset.end()) { // 如果发现出现过就pass | ||
continue; | ||
} | ||
uset.insert(nums[i]); // set跟新元素 | ||
path.push_back(nums[i]); | ||
backtracking(nums, i + 1, used); | ||
path.pop_back(); | ||
} | ||
} | ||
|
||
public: | ||
vector<vector<int>> subsetsWithDup(vector<int>& nums) { | ||
result.clear(); | ||
path.clear(); | ||
vector<bool> used(nums.size(), false); | ||
sort(nums.begin(), nums.end()); // 去重需要排序 | ||
backtracking(nums, 0, used); | ||
return result; | ||
} | ||
}; | ||
|
||
``` | ||
针对留言区录友们的疑问,我再补充一些常见的错误写法, | ||
## 错误写法一 | ||
把uset定义放到类成员位置,然后模拟回溯的样子 insert一次,erase一次。 | ||
例如: | ||
```C++ | ||
class Solution { | ||
private: | ||
vector<vector<int>> result; | ||
vector<int> path; | ||
unordered_set<int> uset; // 把uset定义放到类成员位置 | ||
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) { | ||
result.push_back(path); | ||
for (int i = startIndex; i < nums.size(); i++) { | ||
if (uset.find(nums[i]) != uset.end()) { | ||
continue; | ||
} | ||
uset.insert(nums[i]); // 递归之前insert | ||
path.push_back(nums[i]); | ||
backtracking(nums, i + 1, used); | ||
path.pop_back(); | ||
uset.erase(nums[i]); // 回溯再erase | ||
} | ||
} | ||
``` | ||
|
||
在树形结构中,**如果把unordered_set<int> uset放在类成员的位置(相当于全局变量),就把树枝的情况都记录了,不是单纯的控制某一节点下的同一层了**。 | ||
|
||
如图: | ||
|
||
 | ||
|
||
可以看出一旦把unordered_set<int> uset放在类成员位置,它控制的就是整棵树,包括树枝。 | ||
|
||
**所以这么写不行!** | ||
|
||
## 错误写法二 | ||
|
||
有同学把 unordered_set<int> uset; 放到类成员位置,然后每次进入单层的时候用uset.clear()。 | ||
|
||
代码如下: | ||
|
||
```C++ | ||
class Solution { | ||
private: | ||
vector<vector<int>> result; | ||
vector<int> path; | ||
unordered_set<int> uset; // 把uset定义放到类成员位置 | ||
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) { | ||
result.push_back(path); | ||
uset.clear(); // 到每一层的时候,清空uset | ||
for (int i = startIndex; i < nums.size(); i++) { | ||
if (uset.find(nums[i]) != uset.end()) { | ||
continue; | ||
} | ||
uset.insert(nums[i]); // set记录元素 | ||
path.push_back(nums[i]); | ||
backtracking(nums, i + 1, used); | ||
path.pop_back(); | ||
} | ||
} | ||
``` | ||
uset已经是全局变量,本层的uset记录了一个元素,然后进入下一层之后这个uset(和上一层是同一个uset)就被清空了,也就是说,层与层之间的uset是同一个,那么就会相互影响。 | ||
**所以这么写依然不行!** | ||
**组合问题和排列问题,其实也可以使用set来对同一节点下本层去重,下面我都分别给出实现代码**。 | ||
# 40. 组合总和 II | ||
使用used数组去重版本:[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) | ||
使用set去重的版本如下: | ||
```C++ | ||
class Solution { | ||
private: | ||
vector<vector<int>> result; | ||
vector<int> path; | ||
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) { | ||
if (sum == target) { | ||
result.push_back(path); | ||
return; | ||
} | ||
unordered_set<int> uset; // 控制某一节点下的同一层元素不能重复 | ||
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) { | ||
if (uset.find(candidates[i]) != uset.end()) { | ||
continue; | ||
} | ||
uset.insert(candidates[i]); // 记录元素 | ||
sum += candidates[i]; | ||
path.push_back(candidates[i]); | ||
backtracking(candidates, target, sum, i + 1); | ||
sum -= candidates[i]; | ||
path.pop_back(); | ||
} | ||
} | ||
public: | ||
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { | ||
path.clear(); | ||
result.clear(); | ||
sort(candidates.begin(), candidates.end()); | ||
backtracking(candidates, target, 0, 0); | ||
return result; | ||
} | ||
}; | ||
``` | ||
|
||
# 47. 全排列 II | ||
|
||
使用used数组去重版本:[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA) | ||
|
||
使用set去重的版本如下: | ||
|
||
```C++ | ||
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; | ||
} | ||
}; | ||
``` | ||
# 两种写法的性能分析 | ||
需要注意的是:**使用set去重的版本相对于used数组的版本效率都要低很多**,大家在leetcode上提交,能明显发现。 | ||
原因在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。 | ||
**而使用used数组在时间复杂度上几乎没有额外负担!** | ||
**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 | ||
那有同学可能疑惑 用used数组也是占用O(n)的空间啊? | ||
used数组可是全局变量,每层与每层之间公用一个used数组,所以空间复杂度是O(n + n),最终空间复杂度还是O(n)。 | ||
# 总结 | ||
本篇本打算是对[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)的一个点做一下纠正,没想到又写出来这么多! | ||
**这个点都源于一位录友的疑问,然后我思考总结了一下,就写着这一篇,所以还是得多交流啊!** | ||
如果大家对「代码随想录」文章有什么疑问,尽管打卡留言的时候提出来哈,或者在交流群里提问。 | ||
**其实这就是相互学习的过程,交流一波之后都对题目理解的更深刻了,我如果发现文中有问题,都会在评论区或者下一篇文章中即时修正,保证不会给大家带跑偏!** | ||
就酱,「代码随想录」一直都是干货满满,公众号里的一抹清流,值得推荐给身边的每一位同学朋友! | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters