题目链接
题目分析
给定一个整数数组
nums和一个整数目标值target,需要在数组中找到两个数,使它们的和等于目标值,并返回这两个数的数组下标。题目保证每种输入只会对应一个答案,且同一个元素不能重复使用。
🔍 小知识补充:
这道题是 LeetCode 的“开山之作”,也是全球程序员刷题旅程的起点 🌍。它看似简单,却蕴含了算法设计中非常核心的思想——如何用空间换时间。很多大厂面试的第一轮 coding 题就从它开始!
核心算法及代码讲解
使用哈希表(unordered_map)实现时间复杂度为O(n)的解法。核心思想是利用哈希表的快速查找特性,通过空间换时间。
// 创建哈希表,key存储数值,value存储对应的数组下标
unordered_map<int, int> hashmap;
// 遍历数组中的每个元素
for (int i = 0; i < nums.size(); i++) {
// 计算当前元素所需的补数(目标值减去当前元素值)
int complement = target - nums[i];
// 在哈希表中查找补数是否存在
if (hashmap.find(complement) != hashmap.end()) {
// 找到解,返回两个下标(补数的下标在前,当前下标在后)
return {hashmap[complement], i};
}
// 如果没有找到补数,将当前数值和下标存入哈希表
hashmap[nums[i]] = i;
}
💡 技巧提示:
hashmap.find(key) != hashmap.end()是 C++ 中判断 key 是否存在的标准写法。- 也可以使用
hashmap.count(key)(返回 0 或 1),语义更简洁,性能几乎相同。
解题思路
问题转化:将两数之和问题转化为查找补数问题。对于每个元素nums[i],我们需要在数组中查找是否存在值等于target - nums[i]的补数。
选择数据结构:使用哈希表存储已经遍历过的元素及其下标,利用哈希表O(1)时间复杂度的查找特性。
单次遍历:遍历数组时,对于每个元素先检查其补数是否已在哈希表中。如果存在则直接返回结果,如果不存在则将当前元素加入哈希表。
避免重复使用:由于是先查找后插入,可以保证不会重复使用同一个元素。
🧠 为什么“先查后插”能避免重复?
假设当前元素是 x,如果我们在插入 x 之前就去查 target - x,那么即使 target - x == x,哈希表里也还没有 x(因为还没插入),所以不会误用自己。只有当后面再出现另一个 x 时,前一个 x 已在表中,才能正确匹配。这种顺序设计非常巧妙!
算法分析
- 时间复杂度:O(n),其中n是数组的长度。只需遍历数组一次,每次哈希表操作的时间复杂度为O(1)。
- 空间复杂度:O(n),主要是哈希表的空间开销,最坏情况下需要存储n-1个元素。
📊 对比其他解法:
| 方法 | 时间复杂度 | 空间复杂度 | 是否可行 |
|---|---|---|---|
| 暴力双重循环 | O(n²) | O(1) | ✅ 但效率低 |
| 排序 + 双指针 | O(n log n) | O(n)(需保存原下标) | ⚠️ 可行但麻烦 |
| 哈希表(本解法) | O(n) | O(n) | ✅✅ 最优 |
代码
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 创建哈希表,key存储数值,value存储对应的数组下标
unordered_map<int, int> hashmap;
// 遍历数组中的每个元素
for (int i = 0; i < nums.size(); i++) {
// 计算当前元素所需的补数(目标值减去当前元素值)
int complement = target - nums[i];
// 在哈希表中查找补数是否存在
if (hashmap.find(complement) != hashmap.end()) {
// 找到解,返回两个下标(补数的下标在前,当前下标在后)
return {hashmap[complement], i};
}
// 如果没有找到补数,将当前数值和下标存入哈希表
hashmap[nums[i]] = i;
}
// 根据题目假设,总会有一个解,所以这里通常不会执行到
return {};
}
};
// 测试用例
int main() {
Solution solution;
// 测试用例1:nums = [2,7,11,15], target = 9
vector<int> nums1 = {2, 7, 11, 15};
vector<int> result1 = solution.twoSum(nums1, 9);
// 预期输出:[0,1]
// 测试用例2:nums = [3,2,4], target = 6
vector<int> nums2 = {3, 2, 4};
vector<int> result2 = solution.twoSum(nums2, 6);
// 预期输出:[1,2]
// 测试用例3:nums = [3,3], target = 6
vector<int> nums3 = {3, 3};
vector<int> result3 = solution.twoSum(nums3, 6);
// 预期输出:[0,1]
return 0;
}
🧪 调试建议:
在实际刷题平台(如 LeetCode)上,不需要写 main 函数,只需提交 Solution 类即可。本地测试时保留 main 有助于验证逻辑。
🌱 学习延伸(新增内容)
✅ 举一反三
- 变体1:返回所有不重复的两数组合(需考虑去重)→ 适合用排序+双指针。
- 变体2:数组有序 → 可用双指针,空间复杂度降至 O(1)。
- 变体3:找两数之差等于 target → 同样可用哈希表,但注意方向性。
📚 推荐掌握的数据结构
- C++:
unordered_map(底层哈希)、map(红黑树,有序) - Java:
HashMap - Python:
dict - JavaScript:
Map或普通对象{}
💬 面试高频问法
“如果内存受限,不能使用额外空间,怎么办?”
👉 回答:可以考虑排序后用双指针,但会丢失原始下标,需额外处理(如存储索引对再排序)。
✨ 结语
《两数之和》不仅是 LeetCode 的第一题,更是打开高效算法思维的大门 🔑。掌握它,你就掌握了“哈希思想”的精髓。坚持刷下去,Hot 100 不再是梦!💪
📌 下一期预告:LeetCode 热题 100 第2题 —— 字母异位词分组(中等)
🎯 题目:给定一个字符串数组,将所有字母异位词分组。
🔧 核心思路:使用哈希表按字符排序后的结果作为 key,分组存储。
💡 这是哈希表的经典应用之一,常用于文本处理与字符串匹配场景!