跳到主要内容

1. 两数之和

题目描述​

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例​

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

解题思路​

第一步:理解问题本质​

我们需要在数组中找到两个数,使它们的和等于目标值。核心问题是:如何快速判断一个数的"补数"是否存在?

所谓"补数",就是 target - num,即与当前数相加等于目标值的另一个数。

第二步:暴力解法​

思路:枚举数组中的每一对数字,检查它们的和是否等于目标值。

class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
# 枚举第一个数的下标 i
for i in range(n):
# 第二个数只在 i 之后取,避免同一对数字被正反各算一次,也自然满足「元素不能重复使用」
for j in range(i + 1, n):
# 和为目标值即为答案,题目保证有且仅有一组解,可以直接返回
if nums[i] + nums[j] == target:
return [i, j]
# 走到这里说明没有解;题目保证有解,返回值只为让函数在所有分支上都有返回
return []

为什么不够好:

  • 时间复杂度是 O(n²),当数组很大时效率很低
  • 没有利用任何数据结构来加速查找

第三步:优化解法 —— 排序+双指针​

思路:先排序,然后用双指针从两端向中间移动。

class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 记录原始索引:排序会打乱位置,而题目要返回的是原数组的下标,所以把「值 + 原下标」一起存
indexed_nums = [(num, i) for i, num in enumerate(nums)]
# 按数值排序,让双指针的「左小右大」成立
indexed_nums.sort(key=lambda x: x[0])

# 从两端开始:宽度最大的一对先试起
left, right = 0, len(indexed_nums) - 1
# 每轮至少移动一根指针,相遇即说明所有候选都已验证完毕
while left < right:
current_sum = indexed_nums[left][0] + indexed_nums[right][0]
if current_sum == target:
# 命中,返回两个数的原始下标
return [indexed_nums[left][1], indexed_nums[right][1]]
elif current_sum < target:
# 和偏小:右端已是最大值仍不够,只能把左端换大一点
left += 1
else:
# 和偏大:同理只能把右端换小一点
right -= 1
# 无解时返回空列表,保持返回值类型一致
return []

优点:时间复杂度降为 O(n log n) 缺点:需要排序,改变了原始索引,需要额外空间保存索引

第四步:最优解法 —— 哈希表​

核心洞察:

  • 当我们遍历到数字 num 时,只需要知道 target - num 是否在数组中
  • 哈希表的查找时间复杂度是 O(1)
  • 边遍历边存储,可以实现一次遍历完成

算法步骤:

  1. 创建一个空哈希表,用于存储 {数值: 索引}
  2. 遍历数组,对于每个元素 num:
    • 计算补数 complement = target - num
    • 如果补数在哈希表中,返回 [哈希表[补数], 当前索引]
    • 否则,将 {num: 当前索引} 存入哈希表

为什么正确:

  • 当我们遍历到位置 i 时,哈希表中存储的是 0 到 i-1 的所有元素
  • 如果 target - nums[i] 在哈希表中,说明之前存在某个位置 j,使得 nums[i] + nums[j] = target
  • 由于题目保证有唯一解,且我们按顺序遍历,第一次找到的就是答案

完整代码实现​

算法可视化 · 逐帧回放这道题的代码14 帧 · 录制输入 [3,2,4], 6
from typing import List


class Solution:
"""
两数之和 - 哈希表解法

核心思路:
- 遍历数组时,使用哈希表记录每个数值对应的索引
- 对于当前元素num,检查(target - num)是否在哈希表中
- 如果在,说明找到了答案;如果不在,将当前元素存入哈希表

为什么用哈希表:
- 哈希表的查找时间复杂度是O(1)
- 将整体时间复杂度从O(n²)降低到O(n)

时间复杂度:O(n) - 只需遍历数组一次
空间复杂度:O(n) - 哈希表最多存储n个元素
"""

def twoSum(self, nums: List[int], target: int) -> List[int]:
# 哈希表:存储数值到索引的映射
hashtable = dict()

for i, num in enumerate(nums):
# 计算需要找到的补数
complement = target - num

# 如果补数已在哈希表中,返回两个索引
if complement in hashtable:
return [hashtable[complement], i]

# 将当前数值和索引存入哈希表
hashtable[num] = i

return [] # 题目保证有解,这里为了语法完整


# 测试代码
if __name__ == "__main__":
sol = Solution()

tests = [
([2, 7, 11, 15], 9, [0, 1]),
([3, 2, 4], 6, [1, 2]),
([3, 3], 6, [0, 1]),
]

for nums, target, expected in tests:
result = sol.twoSum(nums, target)
print(f"nums={nums}, target={target} -> {result}, expected={expected}")

示例推演​

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

步骤inumcomplement哈希表状态操作
1027{}7不在表中,存入{2:0}
2172{2:0}2在表中!返回[0,1]

成功找到答案 [0, 1],因为 nums[0] + nums[1] = 2 + 7 = 9。

再以 nums = [3, 2, 4], target = 6 为例:

步骤inumcomplement哈希表状态操作
1033{}3不在表中,存入{3:0}
2124{3:0}4不在表中,存入{3:0, 2:1}
3242{3:0, 2:1}2在表中!返回[1,2]

成功找到答案 [1, 2],因为 nums[1] + nums[2] = 2 + 4 = 6。


复杂度分析​

解法时间复杂度空间复杂度说明
暴力O(n²)O(1)双重循环枚举所有组合
排序+双指针O(n log n)O(n)需要排序和保存索引
哈希表(最优)O(n)O(n)一次遍历完成

易错点总结​

1. 先查表再存数​

错误写法:先存入当前数,再查表

# 错误!存入和查询的顺序反了
hashtable[num] = i # 先把自己存进表
# 再查补数:若 2 * num == target,查到的正是刚刚存下的自己,同一个元素被用了两次
if target - num in hashtable:
...

正确写法:先查表,再存数

# 正确!查的是「之前出现过」的数,天然满足 i != j
if target - num in hashtable: # 先查:表中只有 i 之前的元素
...
# 查完再存自己,当前元素留给后面的元素去配对
hashtable[num] = i # 再存

原因:如果先存,可能会把同一个元素当成答案(例如 target = 6, num = 3,先存后查会找到刚存入的自己)。

2. 返回的是索引,不是数值​

注意题目要求返回的是数组下标,不是数值本身。

3. 答案顺序​

题目说可以按任意顺序返回答案,所以 [0,1] 和 [1,0] 都是正确的。


扩展思考​

1. 如果数组是有序的?​

如果数组已经有序,可以使用双指针法:

  • 左指针指向开头,右指针指向结尾
  • 和小于 target,左指针右移
  • 和大于 target,右指针左移
  • 时间复杂度 O(n),空间复杂度 O(1)

2. 如果要求返回所有可能的组合?​

如果存在多组解,需要修改代码收集所有结果,而不是找到一组就返回。

3. 如果数组中有重复元素?​

哈希表解法天然支持重复元素,因为存储的是 {值: 索引},即使值相同,索引也不同。

4. 三数之和​

类似的问题 15. 三数之和,需要在两数之和的基础上再加一层循环,或使用双指针技巧。


相关题目​

自测​

加载评论中...