Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0018.四数之和 添加哈希解法,不需要额外剪枝 #2470

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion problems/0018.四数之和.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,86 @@ public:
* 时间复杂度: O(n^3)
* 空间复杂度: O(1)

## 哈希解法

记双指针法的用到的四个指针为 a,b,c,d,则双指针法的思路可以概括为:遍历不重复的(a,b)二元,然后用双指针法确定满足条件的(c,d)二元组。

借鉴 [01.两数之和](https://programmercarl.com/0001.两数之和.html) 的方法,可以构造一个包含所有可能(c,d)二元组的哈希表来替代双指针进行检索。这样的一个好处是不需要额外的代码进行剪枝操作。因为剪枝主要是避免对(c,d)二元组进行多余的检索,而哈希表本身就能实现(哈希表只返回满足要求的(c,d)二元组,表里没有就是没有。)本地运行 [测试代码](https://github.com/ZhaiMen-Hub/CppAlgorithmExercise/blob/PR/code/18_4Sum.cpp) 可见哈希解法和剪枝双指针解法的耗时基本相当。

不过由于只能通过c + d的和来检索,只能使代码时间复杂度降低到O(n^3)。如果题目要求条件为 a + b = c + d = 0,则可以直接对(c,d)进行检索,可以用O(n^2)的空间复杂度换取O(n^2)的时间复杂度提升。

实现细节上,由于(c,d)的范围,也即哈希表的大小是决定于 b 的,因此需要交换 a 和 b 的遍历顺序,用最外层的循环遍历 b 。

C++ 代码

```C++
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {

// 创建结果vector
vector<vector<int>> result;

// ordered map: 同时排序和统计数值的出现次数,复杂度O(nlogn)
map<int,int> num_count;
for (int num : nums) num_count[num]++;
vector<pair<int,int>> num_unique(num_count.begin(),num_count.end());

// 边搜边加,相当于用哈希表替代两个夹逼的指针
// 大小关系:pair.second <= pair.first <= iter->first <= iter1->first (d <= c <= b <= a)
unordered_map<long, list<pair<int,int>>> hash_pair;
for (auto iter = num_unique.begin(); iter != num_unique.end(); iter++){

// 指针b在不同位置

for(int i = 1; i <= iter->second; i++) {

// b 只检索倒数第2和第1个位置,防止相同的(b,a)多次检索哈希表导致result重复
if (i >= iter->second - 1){

// 遍历a,查找合适的pair
for(auto iter1 = iter; iter1 != num_unique.end(); iter1++) {

// b 在倒数第二,a只能在倒数第一
// b 在倒数第一,a只能选后面的数
if(i == iter->second - 1 && iter1 != iter || i == iter->second && iter1 == iter) continue;
else {
// 从哈希表返回可能的匹配
const auto& pair_list = hash_pair[long(target) - iter1->first - iter->first];

// 加入result
for (const auto& pair : pair_list) result.push_back(vector<int>{pair.second, pair.first, iter->first, iter1->first});
}

}
}

// 扩充哈希表
if(i == 1) {
// 加入当前数值和之前数值的pair
for(auto iter_pair = num_unique.begin(); iter_pair != iter; iter_pair++){
hash_pair[iter->first + iter_pair->first].push_back(pair(iter->first, iter_pair->first));
}
}
else if (i == 2){
// 加入当前数值自身的pair
hash_pair[iter->first + iter->first].push_back(pair(iter->first, iter->first));
}

}

}

return result;
}
};
```

- 时间复杂度: O(n^3)
- 空间复杂度: O(n^2)




## 补充

Expand Down Expand Up @@ -701,4 +781,3 @@ end
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>