1. 两数之和(Two Sum)

难度:简单 | 主题:数组、哈希表

题目

给定一个整数数组 nums 和一个目标值 target,请在该数组中找出和为目标值的两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,且不能使用同一个元素两次。

示例

输入:nums = [2,7,11,15], target = 9

输出:[0,1]

解释:因为 nums[0] + nums[1] == 9,返回 [0, 1]


思路

先讲个故事:找零钱

你在便利店买了东西,手里有一把零钱硬币。收银员说:“你再给我凑 x 元,我找你一张整钞让你钱包变轻。”

你盯着手里的硬币想:有没有两枚硬币加起来正好 x 元?

最笨的办法:两两组合都试一遍(暴力枚举)。聪明点的办法:每拿起一枚硬币,心里记着”差 x - this 的那枚硬币在哪”,等会碰到就直接配对(哈希表)。

这就是 Two Sum——LeetCode 第一题,也是哈希表思想的经典入门。


引导式推导:从暴力到一次哈希

第 1 层:暴力枚举 O(n²)

看到”两个数”最直接的想法——两重循环枚举所有数对。

 
for i in range(len(nums)):
 
    for j in range(i + 1, len(nums)):
 
        if nums[i] + nums[j] == target:
 
            return [i, j]
 

n=10⁴ 时约 5000 万次比较,还行。n=10⁵ 时 50 亿次,没法忍。

发现规律

对于当前数 nums[i],我们要找的是 target - nums[i]。如果能 O(1) 知道这个”补数”之前出现过,就不用内层循环了。

什么数据结构能 O(1) 查找?哈希表。

第 2 层:两次遍历哈希

先遍历一次,把每个数及其下标存入哈希表;再遍历一次,对每个数查补数是否在表里。

但有个坑:target=6, nums=[3,3] 时,如果查到自己会返回 [0,0],题目禁止同一个元素用两次。

第 3 层:一次遍历哈希(最优)

核心 trick:先查后存


graph LR

    subgraph 一次遍历

        A["拿到 num j"] --> B{"查表有无<br/>target - num"}

        B -->|有| C["返回 [下标, j]"]

        B -->|无| D["存入 hashtable[num] = j"]

        D --> E["下一个 j"]

    end

遍历到 nums[j] 时,只查当前已入表的元素(下标 < j 的),查完再把 nums[j] 入表。这样当前元素不会和自己匹配。


代码

 
def twoSum(self, nums, target):
 
    hashtable = {}
 
    for j, num in enumerate(nums):
 
        complement = target - num
 
        if complement in hashtable:         # 补数之前出现过
 
            return [hashtable[complement], j]
 
        hashtable[num] = j                  # 先查后存,避免自匹配
 

复杂度

指标解释
时间O(n)一次线性遍历
空间O(n)哈希表最多存 n 个键值对
暴力枚举O(n²)两重循环,n=10⁵ 就爆了

实战考量

频率分析

出现在:LeetCode 第一题,几乎所有都会作为热身。但不是考你记不记得代码——关键在于能不能说清”为什么先查后存”和”为什么从暴力到哈希的优化是合理的”

延伸思考

Q:先查后存和先存后查有什么区别?

A:先存后查在 target=6, nums=[3,3] 时会返回 [0,0](同一个元素用了两次)。先查后存保证当前元素尚未入表,不可能自匹配。

Q:如果数组已排序怎么办?

A:双指针,左右夹逼找两数之和,空间 O(1)。详见 167两数之和II-输入有序数组。

Q:如果要求返回所有不重复的数对(不唯一解)呢?

A:排序 + 双指针,或用哈希表存所有下标,找到后配对并跳过重复值。

Q:改成”差为 k 的两个数”怎么做?

A:同款哈希表思路,查 num + knum - k

易错点

  • 哈希表存的是值 → 下标,不是值 → 布尔

  • 先查后存,顺序不能反

  • 题目保证有唯一解,不需要处理无解情况


生活类比

两数之和 → 哈希表 → 先查后存

像你在排队买奶茶,每个人手里拿着一个号码牌。

你心想:“谁手里有 target - 我的号码?”

你每遇到一个人先问”你号码是多少?“,匹配上就组队买一送一;

没匹配上就把自己的号码写在便签上贴墙上,后面的人能看到。

关键洞察:问完再贴——如果先贴自己的,回头看到自己的号码以为匹配到了自己,那就尴尬了。


相关题目

题目关系
167两数之和II-输入有序数组同族双指针版,有序数组用双指针 O(1) 空间
560和为K的子数组前缀和变体,两数之差扩展到连续子数组
15三数之和从两数扩展到三数(双指针夹逼)
01两数之和本题,哈希表入门

→ 返回题单:LeetCode学习路线图 > 一、数组与哈希(含双指针、矩阵)

速记卡(面试闪卡)

Q1:一句话讲清「1. 两数之和(Two Sum)」到底是什么?

A:两数之和用一次哈希遍历,O(1) 查出补数,把配对题从 O(n²) 压成 O(n)。

Q2:题目与本质 —— 怎么理解?

A:给数组和目标值,返回和为目标的两个下标;本质是从”两两试遍”升级成”查表配对”的查找题(Two Sum)。

Q3:思路:暴力到哈希 —— 怎么理解?

A:像便利店凑零钱,每枚硬币心里记着差多少的那枚在哪;用哈希表把 O(n²) 暴力降成 O(n)(Hash Map 哈希表)。

Q4:代码核心 trick —— 怎么理解?

A:排队买奶茶先问再贴号码牌——先查后存,保证当前元素不会和自己匹配,一遍遍历出结果(One-pass Hash 一次遍历哈希)。

Q5:复杂度与实战 —— 怎么理解?

A:时间 O(n)、空间 O(n);面试坑在顺序反了会返回 [0,0],已排序则可改双指针省空间(Time/Space Complexity 复杂度)。

Q6:核心速记主线有哪些?

  • 哈希存「值→下标」,O(1) 查补数

  • 先查后存,避免同一元素用两次

  • 已排序可双指针,空间 O(1)

  • 易错:顺序不能反、存的是下标不是布尔

口诀

A:两数之和配成对,哈希一查补数会;

先问后贴不自救,一遍遍历结果对;

暴力平方太狼狈,O(n) 出招才干脆;

顺序反了出 [0,0],先查后存记心内。

相关链接