【LeetCode02】—— 两数之和:哈希表入门经典详解

【LeetCode】------ 两数之和:哈希表入门经典详解

导语

LeetCode 第 1 题"两数之和"(Two Sum)是无数人刷 LeetCode 的起点。这题看起来简单,但它是理解哈希表思想的绝佳入口。面试中这题基本不会直接考了,但它的变体------两数之和 II、三数之和、四数之和------会反复出现。把第 1 题吃透,后面那些题就是举一反三的事。


一、题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。

假设:每种输入只对应一个答案,且同一个元素不能使用两遍。

示例 1:

复制代码
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:nums[0] + nums[1] == 9

示例 2:

复制代码
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

复制代码
输入:nums = [3,3], target = 6
输出:[0,1]

约束条件:

  • 2 <= nums.length <= 10⁴
  • -10⁹ <= nums[i] <= 10⁹
  • -10⁹ <= target <= 10⁹
  • 只会存在一个有效答案

二、解法一:暴力枚举

最直觉的思路:两层循环,遍历所有两两组合,看哪一对加起来等于 target。

python 复制代码
def twoSum(nums: list[int], target: int) -> list[int]:
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

复杂度:

  • 时间:O(n²) --- 两层循环
  • 空间:O(1)

这解法能过,但效率不高。面试官问"能不能优化",你就知道该往 O(n) 方向想了。


三、解法二:哈希表(推荐解法)

暴力的问题在于:对于每个 nums[i],我们都在后面遍历一遍去找 target - nums[i]。如果能 O(1) 时间查到那个数存不存在,就能把 O(n²) 降到 O(n)。

哈希表就是干这个的。

核心思路

遍历数组,对于每个元素 nums[i]

  1. 计算 complement = target - nums[i]
  2. 查哈希表里有没有 complement --- 有的话直接返回 [hash[complement], i]
  3. 没有的话,把 nums[i] 存进哈希表,继续下一个
python 复制代码
def twoSum(nums: list[int], target: int) -> list[int]:
    hash_map = {}  # 值 -> 下标

    for i, num in enumerate(nums):
        complement = target - num
        if complement in hash_map:
            return [hash_map[complement], i]
        hash_map[num] = i

    return []

图解过程

nums = [2, 7, 11, 15], target = 9 为例:

复制代码
i=0, num=2, complement=7, 哈希表空 → 存入 {2:0}
i=1, num=7, complement=2, 哈希表有 2! → 返回 [0, 1]

两步就找到了。再看一个稍复杂的:

nums = [3, 2, 4], target = 6

复制代码
i=0, num=3, complement=3, 哈希表空 → 存入 {3:0}
i=1, num=2, complement=4, 哈希表没有 4 → 存入 {3:0, 2:1}
i=2, num=4, complement=2, 哈希表有 2! → 返回 [1, 2]

代码实现(Java)

java 复制代码
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();

    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) {
            return new int[]{map.get(complement), i};
        }
        map.put(nums[i], i);
    }

    return new int[]{};
}

代码实现(Go)

go 复制代码
func twoSum(nums []int, target int) []int {
    hashMap := make(map[int]int)

    for i, num := range nums {
        complement := target - num
        if j, ok := hashMap[complement]; ok {
            return []int{j, i}
        }
        hashMap[num] = i
    }

    return nil
}

代码实现(C++)

cpp 复制代码
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> map;

    for (int i = 0; i < nums.size(); i++) {
        int complement = target - nums[i];
        if (map.count(complement)) {
            return {map[complement], i};
        }
        map[nums[i]] = i;
    }

    return {};
}

复杂度:

  • 时间:O(n) --- 只遍历一次
  • 空间:O(n) --- 哈希表最多存 n 个元素

四、为什么是"先查后存"而不是"先存后查"

这是新手最容易写反的地方。

如果先把当前元素存进哈希表,再查 complement,会出现什么问题?

python 复制代码
# 错误写法
for i, num in enumerate(nums):
    hash_map[num] = i  # 先存
    complement = target - num
    if complement in hash_map:  # 后查
        return [hash_map[complement], i]

nums = [3, 3], target = 6 时:

复制代码
i=0, 存入 {3:0}, complement=3, 哈希表有 3! → 返回 [0, 0]

同一个元素用了两次,答案错了。

正确做法是先查再存,这样查到的一定是之前遍历过的元素,不会是自己。


五、两种解法对比

解法 时间复杂度 空间复杂度 适用场景
暴力枚举 O(n²) O(1) 数据量小、面试聊胜于无
哈希表 O(n) O(n) 面试标准答案、数据量大

面试中直接写哈希表解法,暴力的提一嘴就行。


六、常见问题与避坑

1. 同一个元素不能用两次,怎么保证?

先查后存。查的时候哈希表里只有之前遍历过的元素,不可能查到自己。

2. 有重复元素怎么办?

比如 nums = [3, 3], target = 6。哈希表存的是"值 → 下标",第一个 3 存进去后下标是 0,第二个 3 来了,查到 complement 是 3,哈希表里有,返回 [0, 1]。没问题。

但要注意:如果同一元素出现多次,哈希表里只保留最后一次的下标。不过这题保证有且只有一个答案,所以无所谓。

3. 返回的是下标不是值

题目要求返回下标,不是返回元素本身。所以哈希表存的是 值 → 下标,不是 值 → 值

4. 有序数组怎么办?

如果数组是有序的,可以用双指针(左右夹逼),时间 O(n),空间 O(1)。但 LeetCode 第 1 题的数组是无序的,而且题目要求返回原始下标,排序会丢掉下标信息。

LeetCode 第 167 题"两数之和 II"才是有序数组的版本,那题用双指针更合适。

5. 能不能用集合(Set)代替哈希表(Map)?

不行。集合只能判断"有没有",但我们需要知道 complement 的下标是多少。必须用 Map。


七、变体与延伸

两数之和 II(有序数组)

数组有序,用双指针:

python 复制代码
def twoSum(numbers: list[int], target: int) -> list[int]:
    left, right = 0, len(numbers) - 1

    while left < right:
        total = numbers[left] + numbers[right]
        if total == target:
            return [left + 1, right + 1]  # 下标从 1 开始
        elif total < target:
            left += 1
        else:
            right -= 1

    return []

三数之和

在两数之和的基础上多一层循环,先固定一个数,剩下两个用双指针找。注意去重。这是 LeetCode 第 15 题,值得单独写一篇。

四数之和

再套一层。时间 O(n³),但思路和三数之和一样。


八、总结

核心要点:

  1. 哈希表是解决"查找配对"问题的利器,把 O(n²) 降到 O(n)
  2. 记住"先查后存"的顺序,避免同一个元素被用两次
  3. 存的是 值 → 下标,因为题目要求返回下标
  4. 暴力解法能过但效率低,面试写哈希表版本

验证清单:

  • 能手写哈希表解法,不看参考
  • 理解"先查后存"的原因
  • 能解释时间和空间复杂度
  • 知道有序数组版本用双指针

参考资源

相关推荐
zhengzhouliuhaha3 小时前
智能医疗设备控费系统:以全院一体化管控,筑牢医疗资源“安全阀”
大数据·数据结构·人工智能·算法·安全·机器学习·软件需求
Yiyaoshujuku4 小时前
化合物数据集API接口(数据结构及样例)
java·网络·数据结构
fu的博客4 小时前
【数据结构16】图:基于邻接矩阵、邻接表实现DFS/BFS
数据结构·算法
言存6 小时前
力扣热题283 移动零
数据结构·算法·leetcode
Lewiis6 小时前
白话桶排序
数据结构·算法·golang·排序算法
iiiiyu7 小时前
IO流相关编程题
java·大数据·开发语言·数据结构·数据库·mysql
Darling噜啦啦8 小时前
JS 数据结构实战:从栈队列到链表,一文吃透数组底层原理与线性数据结构
前端·javascript·数据结构
洛水水8 小时前
【力扣100题】80.寻找旋转排序数组中的最小值
数据结构·算法·leetcode
legend050709ComeON8 小时前
常见面试题-leetcode
数据结构·算法·leetcode