【LeetCode Hot100刷题日记(1/100)】两数之和——哈希

211 阅读5分钟

题目链接

leetcode.cn/problems/tw… image.png

题目分析

给定一个整数数组 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),语义更简洁,性能几乎相同。

解题思路

  1. 问题转化:将两数之和问题转化为查找补数问题。对于每个元素nums[i],我们需要在数组中查找是否存在值等于target - nums[i]的补数。

  2. 选择数据结构:使用哈希表存储已经遍历过的元素及其下标,利用哈希表O(1)时间复杂度的查找特性。

  3. 单次遍历:遍历数组时,对于每个元素先检查其补数是否已在哈希表中。如果存在则直接返回结果,如果不存在则将当前元素加入哈希表。

  4. 避免重复使用:由于是先查找后插入,可以保证不会重复使用同一个元素。

🧠 为什么“先查后插”能避免重复?
假设当前元素是 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(红黑树,有序)
  • JavaHashMap
  • Pythondict
  • JavaScriptMap 或普通对象 {}

💬 面试高频问法

“如果内存受限,不能使用额外空间,怎么办?”
👉 回答:可以考虑排序后用双指针,但会丢失原始下标,需额外处理(如存储索引对再排序)。


结语
《两数之和》不仅是 LeetCode 的第一题,更是打开高效算法思维的大门 🔑。掌握它,你就掌握了“哈希思想”的精髓。坚持刷下去,Hot 100 不再是梦!💪

📌 下一期预告:LeetCode 热题 100 第2题 —— 字母异位词分组(中等)
🎯 题目:给定一个字符串数组,将所有字母异位词分组。
🔧 核心思路:使用哈希表按字符排序后的结果作为 key,分组存储。
💡 这是哈希表的经典应用之一,常用于文本处理与字符串匹配场景!