刷完训练营第一天的数组专题,第二天迎面就是哈希表的三道题目:454. 四数相加 II、15. 三数之和、18. 四数之和。说实话,刚看到题号的时候我并没有太在意,毕竟“两数之和”谁都会写,哈希表查重也谈不上难。真正上手之后才意识到,这三道题放在一起是有讲究的——它们呈现了哈希表从“计数辅助”到“双指针降维”再到“剪枝优化”的完整梯度,尤其是15题和18题的去重细节,几乎能让每个第一次写的人都在测试用例上翻车。这篇就按我实际刷题时的思路顺序,把这三道题从暴力到最优的推导过程、代码实现和踩坑记录完整复盘一遍。
1. 为什么训练营第二天就上三块硬骨头
先聊一个很多刷题新手会困惑的问题:这三道题明明都是“找几个数凑某个和”,为什么归到哈希表专题?其实哈希表在这三道题里扮演的角色完全不同。
454题是哈希表的本职工作场景——统计两个数组合并后的和频次,再用另一个两数之和去匹配,这个思路非常“哈希”。而15题和18题如果硬用哈希表做,你会发现去重逻辑非常痛苦,反而排序后配合双指针更加干净,哈希表在这里只承担“辅助判断”的次要角色。训练营把三道题放在同一天,其实是在传递一个信号:数据结构是工具,不是目的。什么时候用哈希表、什么时候放弃哈希表改双指针,这种判断力比背模板重要得多。
再说难度曲线。454题是标准的“用空间换时间”入门题,理解了分组匹配的复杂度推导后基本可以一次写对。15题是面试高频,尤其是去重的边界条件,很容易在while循环里写错。18题是15题的套娃版本——多套一层循环,但剪枝细节需要单独处理。三道题刷完,你对“哈希表解决配对问题”的整套认知会清晰很多。
我在代码随想录的开篇导读里看到过一句话:训练营每天的题量不算大,但每道题都值得反复咀嚼。第二天的这三道题尤其符合这个判断——表面看都是“相加等于某个数”的套路,实际每道题的思考方式都有本质差异,值得一篇长文展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 四数相加 II:HashMap 的计数用法才是重点
先看原题描述:给你四个整数数组 nums1、nums2、nums3、nums4,数组长度都是 n,请你计算有多少个元组 (i, j, k, l) 能满足 nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0。
注意这里有个硬性条件:只需要返回元组数量,不需要返回具体下标组合。这一点非常重要,直接决定了我们能用哈希表做“计数配对”,而不必费劲去重。
2.1 暴力四重循环为什么不行
最直观的做法当然是四层for循环,枚举所有 (i, j, k, l) 组合,判断和是否为零,能 AC 的前提是 n 足够小。但这题的数组长度限制是 n <= 200,四层循环就是 200^4 = 16亿次枚举,显然会超时。
从复杂度上看,四重循环是 O(n^4),这在算法竞赛里是会被直接枪毙的复杂度。哪怕 n = 100,1亿次操作也要跑好几秒,更别说 200 的数据规模。
有些同学可能会想:能不能给四个数组都排序,然后枚举前三个数,最后一个数组用二分查找?这个思路能把复杂度降到 O(n^3 log n),但实现起来要处理重复元素的情况,而且仍然不是最优解。真正漂亮的解法,是两两分组后用哈希表。
2.2 从 O(n^4) 到 O(n^2) 的核心推导
我一直觉得,理解这题最好的切入点是“拆成两个两数之和”。
四个数相加等于0,本质上等价于:
nums1[i] + nums2[j] = -(nums3[k] + nums4[l])
也就是说,我们不需要同时关心四个数组,只需要先用哈希表记录“前两个数组所有和的出现次数”,再遍历“后两个数组所有和”,查表寻找相反数。伪代码思路:
cpp复制// 第一步:用哈希表记录 nums1 + nums2 的所有和及出现次数
unordered_map<int, int> ab_sum;
for (int a : nums1) {
for (int b : nums2) {
ab_sum[a + b]++;
}
}
// 第二步:遍历 nums3 + nums4 的和,查表找相反数
int count = 0;
for (int c : nums3) {
for (int d : nums4) {
int target = -(c + d);
if (ab_sum.find(target) != ab_sum.end()) {
count += ab_sum[target];
}
}
}
为什么这个做法是完全正确的?因为对任意一组满足条件的 (i, j, k, l),一定有 nums1[i] + nums2[j] 存在于哈希表中,且它的值等于 -(nums3[k] + nums4[l])。枚举后两个数组的所有组合时,恰好会命中这个和,并且通过哈希表里记录的“出现次数”把不同下标组合全部计入。
这一步的时间复杂度是 O(n^2),两个双重循环分别负责建表与查表;空间复杂度是 O(n^2),因为最坏情况下 nums1 + nums2 的和可能有 n^2 种不同取值。相比 O(n^4) 的暴力,这是一个数量级层面的改善——n=200 时,运算量从16亿次降到4万次,完全不是一个量级。
