📖 第1课:两数之和

3 阅读20分钟

想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。

📖 第1课:两数之和

模块:哈希表入门 | 难度:Easy ⭐ LeetCode 链接leetcode.cn/problems/tw… 前置知识:无(本课是起点课) 预计学习时间:20分钟


🎯 题目描述

给你一个整数数组和一个目标值,请你在数组中找出两个数,使它们的和恰好等于目标值。返回这两个数的下标(不是值本身)。

题目保证:每组输入恰好有唯一解,同一个元素不能用两次

示例:

输入:nums = [2, 7, 11, 15], target = 9
输出:[0, 1]
解释:nums[0] + nums[1] = 2 + 7 = 9,所以返回下标 [0, 1]
输入:nums = [3, 2, 4], target = 6
输出:[1, 2]
解释:nums[1] + nums[2] = 2 + 4 = 6

约束条件:

  • 2 <= nums.length <= 10^4(数组最少2个元素,最多1万个)
  • -10^9 <= nums[i] <= 10^9(元素可以为负数或零)
  • -10^9 <= target <= 10^9
  • 只会存在一个有效答案

🧪 边界用例(面试必考)

用例类型输入期望输出考察点
最小输入nums=[1,2], target=3[0,1]数组只有2个元素,直接配对
含负数nums=[-3,4,3,90], target=0[0,2]负数 + 正数 = 0,别忘了负数
含零nums=[0,4,3,0], target=0[0,3]0+0=0,两个不同下标的0
含重复值nums=[3,3], target=6[0,1]值相同但下标不同,不能用同一元素
目标为负nums=[-1,-2,-3], target=-4[0,2]target本身可以是负数
大规模n=10^4暴力法约 5×10^7 次,刚好卡边界

💡 思路引导

生活化比喻

想象你在一个热闹的跳蚤市场买东西,手里有 9 块钱(target=9),你要找到两件商品的价格加起来正好等于 9 块。

🐌 笨办法:你拿起第一件商品(比如标价 2 元),然后把市场里所有其他商品都翻一遍,看看有没有标价 7 元的。没找到?放下,再拿第二件,又从头翻一遍……这样逛下去,你可能累断腿才找到。每拿一件商品,都要扫一遍整个市场,效率极低。

🚀 聪明办法:你带了一个小本本(哈希表)。每走到一个摊位,先不急着全市场找配对——而是低头翻翻小本本:"之前有没有见过能和这件凑成 9 块的?" 没有?就把当前商品的价格和摊位号记到小本本里,然后继续逛。因为小本本翻一下只需要一瞬间,所以你只需要逛市场一圈,就能找到答案。这就是"空间换时间"的威力——多带个小本本,省下无数条腿。

关键洞察

与其对每个数都去数组里"搜索"它的互补数,不如一边遍历一边"记住"见过的数,让互补数自己来找你。


🧠 解题思维链

这一节模拟你在面试中"从零开始思考"的过程。

Step 1:理解题目 → 锁定输入输出

  • 输入:一个整数数组 nums(长度 2~10^4),一个整数 target
  • 输出:两个下标 [i, j](不是值!面试中经常有人搞混)
  • 限制:恰好一个解;同一元素不能用两次(i != j);元素可以重复、可以为负

Step 2:先想笨办法(暴力法)

最直觉的做法——两两配对试一试。用两层循环,外层选第一个数、内层选第二个数,看它们的和是否等于 target。

  • 时间复杂度:O(n^2)
  • 瓶颈在哪:内层循环在做"查找某个特定值是否存在",每次查找都要线性扫描

Step 3:瓶颈分析 → 优化方向

暴力法的核心问题:对于外层遍历的每一个 nums[i],我们需要在剩余元素中查找 target - nums[i]。这个"查找"操作是 O(n),导致总体 O(n^2)。

  • 核心问题:"查找互补数"这一步太慢了——每次都要遍历一遍
  • 优化思路:能不能把"查找"从 O(n) 降到 O(1)?→ 能!用哈希表存储已经见过的数

Step 4:选择武器

  • 选用:哈希表(Python 字典 dict)
  • 理由:字典支持 O(1) 的键查找,正好把"查找互补数"这个瓶颈从 O(n) 压缩到 O(1),总体从 O(n^2) 降为 O(n)

🔑 模式识别提示:当题目出现"查找配对"、"两数之和/差/积"、"是否存在某个互补值",优先考虑"一遍哈希表"模式


🔑 解法一:暴力双循环(直觉法)

思路

最朴素的想法:把所有两两组合都试一遍。外层循环选第一个数 nums[i],内层循环选第二个数 nums[j]j > i 避免重复配对),检查 nums[i] + nums[j] 是否等于 target

图解过程

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

Step 1: i=0, nums[i]=2
  j=1: 2+7=9 ✅ 命中!返回 [0, 1]

  +-----+-----+------+------+
  | [2] | [7] | [11] | [15] |
  +-----+-----+------+------+
    i=0   j=1
           ↓
        2+7=9 == target ✅ → return [0, 1]
示例:nums = [3, 2, 4], target = 6

Step 1: i=0, nums[i]=3
  j=1: 3+2=5j=2: 3+4=7 ❌

  +-----+-----+-----+
  | [3] | [2] | [4] |
  +-----+-----+-----+
    i=0   j=1   j=2
          3+2=53+4=7 ❌

Step 2: i=1, nums[i]=2
  j=2: 2+4=6 ✅ 命中!返回 [1, 2]

  +-----+-----+-----+
  | [3] | [2] | [4] |
  +-----+-----+-----+
          i=1   j=2
                 ↓
              2+4=6 == target ✅ → return [1, 2]

Python代码

from typing import List


def two_sum_brute(nums: List[int], target: int) -> List[int]:
    """
    解法一:暴力双循环
    思路:枚举所有 (i,j) 组合,检查 nums[i]+nums[j] 是否等于 target
    """
    n = len(nums)                             # 数组长度
    for i in range(n):                        # 外层:选第一个数
        for j in range(i + 1, n):             # 内层:选第二个数(j>i 避免重复)
            if nums[i] + nums[j] == target:   # 检查是否命中
                return [i, j]                 # 题目保证有解,直接返回
    return []                                 # 理论上不会到这里(保险写法)


# ✅ 测试
print(two_sum_brute([2, 7, 11, 15], 9))    # 期望输出:[0, 1]
print(two_sum_brute([3, 2, 4], 6))          # 期望输出:[1, 2]
print(two_sum_brute([3, 3], 6))             # 期望输出:[0, 1]  (重复值)
print(two_sum_brute([-3, 4, 3, 90], 0))     # 期望输出:[0, 2]  (含负数)
print(two_sum_brute([0, 4, 3, 0], 0))       # 期望输出:[0, 3]  (0+0=0)

复杂度分析

  • 时间复杂度:O(n^2) — 两层循环,每层最多 n 次
    • 具体地说:如果 n=10000,最坏需要约 10000×9999/2 ≈ 5×10^7 次操作,勉强能过但很慢
  • 空间复杂度:O(1) — 只用了几个变量,没有额外数据结构

优缺点

  • ✅ 思路直观,代码简单,不容易出bug
  • ✅ 不需要额外空间
  • ❌ 时间复杂度 O(n^2),数据量大时太慢——内层循环的"查找"是瓶颈,能不能用更快的方式查找?

⚡ 解法二:一遍哈希表(最优解)

优化思路

暴力法的痛点在于"查找互补数"太慢。我们用一个字典(哈希表)来记住已经遍历过的数:键是元素值,值是下标。这样每到一个新元素,只需在字典里 O(1) 查找它的互补数 target - nums[i] 是否已经出现过。

💡 关键想法:一边遍历一边建字典,对当前元素先查再存——如果先存再查,会错误地把自己和自己配对。

图解过程

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

  字典 seen = {}(一开始是空的)

  ╔══════════════════════════════════════════════════════════════╗
  ║ i=0, num=2                                                  ║
  ║   complement = 9 - 2 = 7                                    ║
  ║   7 在 seen 里吗?seen = {} → ❌ 不在                        ║
  ║   → 把 2 记进去:seen = {2: 0}                               ║
  ╠══════════════════════════════════════════════════════════════╣
  ║ i=1, num=7                                                  ║
  ║   complement = 9 - 7 = 2                                    ║
  ║   2 在 seen 里吗?seen = {2: 0} → ✅ 在!下标是 0            ║
  ║   → 返回 [0, 1]                                             ║
  ╚══════════════════════════════════════════════════════════════╝

  结果:[0, 1]
示例:nums = [3, 2, 4], target = 6

  字典 seen = {}

  ╔══════════════════════════════════════════════════════════════╗
  ║ i=0, num=3                                                  ║
  ║   complement = 6 - 3 = 3                                    ║
  ║   3 在 seen 里吗?seen = {} → ❌                             ║
  ║   → seen = {3: 0}                                           ║
  ╠══════════════════════════════════════════════════════════════╣
  ║ i=1, num=2                                                  ║
  ║   complement = 6 - 2 = 4                                    ║
  ║   4 在 seen 里吗?seen = {3: 0} → ❌                         ║
  ║   → seen = {3: 0, 2: 1}                                    ║
  ╠══════════════════════════════════════════════════════════════╣
  ║ i=2, num=4                                                  ║
  ║   complement = 6 - 4 = 2                                    ║
  ║   2 在 seen 里吗?seen = {3:0, 2:1} → ✅ 在!下标是 1        ║
  ║   → 返回 [1, 2]                                             ║
  ╚══════════════════════════════════════════════════════════════╝

  结果:[1, 2]
边界用例:nums = [3, 3], target = 6

  ╔══════════════════════════════════════════════════════════════╗
  ║ i=0, num=3                                                  ║
  ║   complement = 6 - 3 = 3                                    ║
  ║   3 在 seen 里吗?seen = {} → ❌(还没放进去呢!)             ║
  ║   → seen = {3: 0}                                           ║
  ╠══════════════════════════════════════════════════════════════╣
  ║ i=1, num=3                                                  ║
  ║   complement = 6 - 3 = 3                                    ║
  ║   3 在 seen 里吗?seen = {3: 0} → ✅ 在!下标是 0            ║
  ║   → 返回 [0, 1]                                             ║
  ╚══════════════════════════════════════════════════════════════╝

  关键:先查再存,所以 i=0 时不会把自己和自己配对 ✅

Python代码

from typing import List


def two_sum_hash(nums: List[int], target: int) -> List[int]:
    """
    解法二:一遍哈希表
    思路:遍历数组,对每个元素查字典里有没有它的互补数
    """
    seen = {}                                 # 字典:值 → 下标
    for i, num in enumerate(nums):            # 同时获取下标和值
        complement = target - num             # 计算互补数
        if complement in seen:                # O(1) 查找:互补数见过吗?
            return [seen[complement], i]      # 见过!返回两个下标
        seen[num] = i                         # 没见过,记住当前数和下标
    return []                                 # 保险写法


# ✅ 测试
print(two_sum_hash([2, 7, 11, 15], 9))    # 期望输出:[0, 1]
print(two_sum_hash([3, 2, 4], 6))          # 期望输出:[1, 2]
print(two_sum_hash([3, 3], 6))             # 期望输出:[0, 1]  (重复值)
print(two_sum_hash([-3, 4, 3, 90], 0))     # 期望输出:[0, 2]  (含负数)
print(two_sum_hash([0, 4, 3, 0], 0))       # 期望输出:[0, 3]  (0+0=0)

复杂度分析

  • 时间复杂度:O(n) — 只遍历一次数组,每次字典查找 O(1)
    • 具体地说:如果 n=10000,只需要约 10000 次操作,比暴力法快 5000 倍!
  • 空间复杂度:O(n) — 字典最多存 n 个键值对
    • 典型的"空间换时间"策略

🐍 Pythonic 写法

利用 Python 字典推导式和 enumerate 的简洁写法:

from typing import List


def two_sum_pythonic(nums: List[int], target: int) -> List[int]:
    """
    Pythonic 写法:利用字典推导 + enumerate
    注意:这里先建完整字典再查找,适用于无重复值或需要后出现的下标
    """
    # 一行建立 {值: 下标} 的映射(重复值会保留最后出现的下标)
    index_map = {val: idx for idx, val in enumerate(nums)}

    # 遍历查找互补数
    for i, num in enumerate(nums):
        complement = target - num
        # complement 存在且不是自己(用下标判断,避免自己配自己)
        if complement in index_map and index_map[complement] != i:
            return [i, index_map[complement]]
    return []


# ✅ 测试
print(two_sum_pythonic([2, 7, 11, 15], 9))    # 期望输出:[0, 1]
print(two_sum_pythonic([3, 2, 4], 6))          # 期望输出:[1, 2]
print(two_sum_pythonic([3, 3], 6))             # 期望输出:[0, 1]

这个写法用到了:

  • 字典推导式 {val: idx for idx, val in enumerate(nums)}:一行代码把列表变成值→下标的映射
  • enumerate():同时获取下标和值,比手动维护计数器更 Pythonic
  • 注意判断 index_map[complement] != i,防止同一个元素被用两次

⚠️ 面试建议:先写解法二(一遍哈希表)展示清晰的思路和"先查再存"的技巧,再提一嘴"也可以用字典推导式先建完映射再查"来展示语言功底。面试官更看重你的思考过程,而非代码行数。


📊 解法对比

维度解法一:暴力双循环解法二:一遍哈希表Pythonic写法
时间复杂度O(n^2)O(n)O(n)
空间复杂度O(1)O(n)O(n)
代码难度简单简单简单
面试推荐⭐⭐⭐⭐⭐
适用场景只适合小规模数据或说明思路面试首选,通用性强快速编码、代码竞赛

面试建议:先用 30 秒口述暴力法思路和复杂度(表明你能想到基本解法),然后直接优化到一遍哈希表(展示优化能力)。不需要把暴力法代码完整写出来,把时间留给最优解的编码和测试。


🎤 面试现场

模拟面试中的完整对话流程,帮你练习"边想边说"。

面试官:来看一道经典题——给你一个整数数组和一个目标值,找出两个数使它们的和等于目标值,返回它们的下标。

:(审题 30 秒)好的,让我确认一下——输入是一个整数数组和一个 target,输出是两个不同的下标,对吧?每组输入保证有唯一解?

面试官:没错。

:好的。我先想一个最直接的办法:两层 for 循环,把所有二元组都试一遍,时间 O(n^2),空间 O(1)。不过这个显然不够快。

让我想想怎么优化……瓶颈在于:对于每个数 nums[i],我需要查找 target - nums[i] 是否存在。查找操作是 O(n),如果我能把查找降到 O(1) 就好了——用哈希表

具体做法是:一边遍历数组,一边维护一个字典,键是值、值是下标。对每个元素,先看字典里有没有它的互补数。有就直接返回两个下标;没有就把当前数存进字典,继续。这样整体时间 O(n),空间 O(n)。

面试官:思路清晰,请写代码吧。

:好的。(边写边说)

def twoSum(self, nums, target):
    seen = {}                            # 值 → 下标
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:           # O(1) 查找
            return [seen[complement], i]
        seen[num] = i

我用 enumerate 同时拿到下标和值。关键是"先查再存"——这样可以避免一个元素和自己配对。

面试官:能手动跑一个例子验证一下吗?

:好的,用 nums=[2, 7, 11, 15], target=9

  • i=0, num=2:complement=7,seen={} 里没有 7 → 存入 seen={2:0}
  • i=1, num=7:complement=2,seen={2:0} 里有 2 → 返回 [0, 1]

正确!再测个边界:nums=[3,3], target=6

  • i=0, num=3:complement=3,seen={} 里没有 3 → 存入 seen={3:0}
  • i=1, num=3:complement=3,seen={3:0} 里有 3 → 返回 [0, 1]

没有出现"自己配自己"的问题,因为 i=0 时先查(空字典,查不到)再存。✅

面试官:很好。如果我不让你用额外空间呢?

:如果要求 O(1) 空间,可以先排序(O(n log n)),再用双指针从两端向中间逼近。但要注意排序会打乱原始下标,所以需要在排序前记录下标(比如排序 (value, index) 元组)。时间变成 O(n log n),空间 O(n) 或 O(1) 取决于排序实现。对这道题来说,哈希表解法的 O(n) 时间其实更优。

高频追问

追问应答策略
"还有更优解吗?"时间已经是 O(n) 最优(至少要看一遍所有元素),空间 O(n) 换来的速度提升很值得。如果题目允许改原数组,可以用排序+双指针做到 O(n log n) 时间、O(1) 额外空间,但时间反而更慢
"如果数据量是 10 亿级别呢?"单机内存放不下哈希表,需要分桶(partition):按 nums[i] % K 分成 K 个桶,互补的两个数一定在"对称桶"里,可以分布式处理
"如果有多组解怎么办?"return 改成 append 收集所有解;字典的值改为下标列表 defaultdict(list) 处理重复值
"实际工程中什么场景会用到?"任何"查找配对"场景:交易撮合系统匹配买卖单、推荐系统找互补用户、网络协议中匹配请求和响应

🎓 知识点总结

Python技巧卡片 🐍

# enumerate() — 同时拿到下标和值,告别手动 i += 1
for i, val in enumerate(["a", "b", "c"]):
    print(i, val)   # 0 a / 1 b / 2 c

# dict 的 in 操作 — O(1) 判断键是否存在
seen = {2: 0, 7: 1}
print(7 in seen)     # True    ← 查的是键,不是值!
print(0 in seen)     # False   ← 0 是值不是键

# 字典推导式 — 一行创建字典
nums = [10, 20, 30]
index_map = {v: i for i, v in enumerate(nums)}
# 结果:{10: 0, 20: 1, 30: 2}

# dict.get(key, default) — 安全取值,键不存在时返回默认值
seen = {2: 0}
print(seen.get(7, -1))   # -1(不存在时返回 -1,不会报错)

💡 底层原理(选读)

哈希表为什么能 O(1) 查找?

你可以把哈希表想象成一个超大的储物柜。每个柜子有编号(0, 1, 2, ...)。当你要存一个东西(比如数字 7),哈希表会用一个哈希函数算出一个编号——比如 hash(7) % 柜子总数 = 3——然后直接放到 3 号柜子里。

下次要找 7 的时候,不需要一个一个柜子翻过去,而是再算一次哈希,直接走到 3 号柜子打开看。一步到位,这就是 O(1) 的秘密。

那什么时候不是 O(1)? 当两个不同的值算出相同的编号("哈希冲突"),它们会被塞到同一个柜子里(用链表或其他方式串起来)。如果冲突非常多,最坏情况下一个柜子里全是东西,查找就退化为 O(n)。但 Python 的 dict 使用了开放寻址法 + 精心设计的哈希函数,在绝大多数实际场景下冲突很少,平均查找时间稳定在 O(1)。

Python 中 dict 和 set 的区别

  • dict:存键值对(key → value),适合你需要"记住额外信息"的场景(如本题中记住下标)
  • set:只存(key),适合你只需要判断"有没有见过"的场景
  • 两者底层都是哈希表,查找都是 O(1)

算法模式卡片 📐

  • 模式名称:一遍哈希表(One-pass Hash Map)
  • 适用条件:在数组/序列中查找满足某种"配对关系"的两个(或多个)元素
  • 识别关键词:两数之和/差/积、查找配对、是否存在互补值、频次统计
  • 模板代码
def one_pass_hash(nums, target):
    """
    一遍哈希表模板:
    - 遍历数组,对每个元素计算"需要的配对值"
    - 先查字典(看配对值是否已存在),再存当前元素
    - 关键:先查再存,避免自己配自己
    """
    seen = {}                               # {值: 下标} 或 {值: 出现次数}
    for i, num in enumerate(nums):
        need = target - num                 # 计算配对值(根据题意调整运算)
        if need in seen:                    # O(1) 查找
            return [seen[need], i]          # 找到配对,返回结果
        seen[num] = i                       # 未找到,存入当前元素
    return []                               # 无解

易错点 ⚠️

  1. 返回下标 vs 返回值:题目要求返回的是下标,不是值本身。很多人匆忙写代码时直接 return [num, complement],面试直接扣分。解决:审题时在草稿纸上写清楚"输入/输出"再动手。

  2. 同一元素用两次:比如 nums=[3,2,4], target=6,如果你先把所有数存进字典再查找,可能会把 nums[0]=3 和它自己配对得到 3+3=6(但实际上只有一个 3)。解决:先查再存,或者查找时检查 index_map[complement] != i

  3. 重复值覆盖:如果先建完字典再查找(Pythonic 写法),nums=[3,3] 中第二个 3 会覆盖第一个 3 的下标。需要注意这不影响正确性——因为我们从前往后遍历查找时,i=0 的 complement=3 在字典里的下标是 1(被覆盖后的),返回 [0, 1] 正好正确。但如果对覆盖行为不放心,推荐使用"一遍哈希"的先查再存写法,逻辑更清晰可控。

  4. 忘记处理负数:题目的 nums 和 target 都可以是负数。target - num 在负数情况下依然成立,不需要特殊处理,但测试时要覆盖负数用例。


🏗️ 工程实战(选读)

这个算法思想在真实项目中的应用,让你知道"学了有什么用"。

  • 数据库索引:数据库对某一列建立索引(本质是哈希表或B树),使得 SELECT * FROM users WHERE id = 12345 不需要全表扫描,而是 O(1) 或 O(log n) 直接定位到目标行。两数之和的核心思想——"用空间换时间,把查找从线性变为常数"——正是数据库索引的灵魂。

  • 交易撮合系统:证券/加密货币交易所的核心模块是"撮合引擎"。当一笔买单进来(比如"想以 100 元买 A 股"),系统需要快速找到是否有匹配的卖单("有人以 100 元卖 A 股吗?")。这就是一个"查找配对"问题,通常用哈希表按价格索引所有挂单,实现 O(1) 撮合。

  • 网络协议中的请求-响应匹配:HTTP/2 和 gRPC 等协议中,客户端发送的请求带有唯一 ID,服务端返回响应时也带同一个 ID。客户端用一个哈希表 {请求ID: 回调函数} 来存储等待中的请求,收到响应时 O(1) 找到对应的回调并执行。

  • 缓存系统:Redis、Memcached 等缓存本质上就是一个大哈希表。cache.get("user:1001") 的 O(1) 查找能力让热数据访问延迟从毫秒级(数据库查询)降到微秒级。


🏋️ 举一反三

完成本课后,试试这些同类题目来巩固知识:

题目难度相关知识点提示
LeetCode 167. 两数之和 II - 输入有序数组Medium双指针数组已排序,不需要哈希表,用左右双指针更优
LeetCode 15. 三数之和Medium排序 + 双指针固定一个数,内层变成两数之和;注意去重
LeetCode 18. 四数之和Medium排序 + 双指针三数之和再套一层循环,关键是剪枝和去重
LeetCode 454. 四数相加 IIMedium哈希表四个独立数组,两两分组用哈希表,思路和两数之和一模一样
LeetCode 560. 和为 K 的子数组Medium前缀和 + 哈希表"前缀和之差=K" 转化为两数之和问题,非常经典
LeetCode 217. 存在重复元素Easy哈希表(set)最简单的哈希表应用:见过就返回 True

📝 课后小测

试试这道变体题,不要看答案,自己先想 5 分钟!

题目:给定一个整数数组 nums 和一个目标值 target,判断数组中是否存在两个数的恰好等于 targettarget > 0)。如果存在,返回 True,否则返回 False

示例:nums = [5, 2, 8, 1], target = 3True(因为 5 - 2 = 3)

💡 提示 1(实在想不出来再点开)

"两数之差等于 target" 可以改写为 a - b = target,也就是 a = b + target。是不是有点像两数之和?

💡 提示 2(再给你一个线索)

遍历数组时,对于当前的数 num,它的"配对数"有两种情况:num + target(当前数是较小的那个)和 num - target(当前数是较大的那个)。在哈希表里查找这两个值就行了。

✅ 参考答案
def two_diff(nums: list[int], target: int) -> bool:
    """
    两数之差:a - b = target
    等价于查找:对每个 num,是否存在 num - target 或 num + target
    """
    seen = set()                           # 只需判断存在性,用 set 足够
    for num in nums:
        # num 作为较大值:num - x = target → x = num - target
        # num 作为较小值:x - num = target → x = num + target
        if (num - target) in seen or (num + target) in seen:
            return True
        seen.add(num)
    return False


# 测试
print(two_diff([5, 2, 8, 1], 3))     # True  (5-2=3)
print(two_diff([1, 2, 3, 4], 10))    # False (没有差为10的配对)
print(two_diff([1, 5, 3, 4, 2], 0))  # False (target>0,不需要考虑0)

核心思路:把"两数之差"问题转化为和两数之和一样的"哈希查找配对"模式。区别在于配对值有两个方向(num ± target),而两数之和只有一个方向(target - num)。这里只需要判断存在性不需要返回下标,所以用 setdict 更合适。


如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。