作为一名经历过上百场算法面试的老兵,我清楚地记得"两数之和"这道题在各大技术面试中的出现频率高达78%。这道看似简单的题目,实际上考察了开发者对数据结构选择和时间复杂度优化的基本功。
题目要求很简单:给定一个整数数组nums和一个目标值target,找出数组中两个数之和等于target,并返回它们的下标。例如:
大多数初学者(包括当年的我)的第一反应都是使用双重循环:
python复制def twoSum(nums, target):
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
这种解法的时间复杂度是O(n²),空间复杂度是O(1)。当数组长度n较小时(比如n<1000),这种解法完全可行。但在实际工程场景中,当n达到10⁶级别时,这种解法就会变得极其低效。
注意:在技术面试中,如果只给出这种解法,通常只能得到"勉强及格"的评价。面试官期待的是更优的解决方案。
哈希表(Hash Table)是一种通过键值对存储数据的数据结构,它能在平均O(1)时间内完成查找操作。对于这个问题,我们可以利用哈希表来存储已经遍历过的数字及其索引,这样在遍历数组时,可以快速检查target - current_num是否存在于哈希表中。
python复制def twoSum(nums, target):
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
让我们对比两种解法在n=10⁶时的表现:
在实际测试中,当n=10⁶时:
在Python中,字典(dict)就是基于哈希表实现的。当我们执行complement in hash_map操作时:
这个过程的平均时间复杂度是O(1),最坏情况下(哈希冲突严重)会退化到O(n),但在实际应用中很少发生。
在实际编码中,我们需要考虑各种边界情况:
虽然题目说明每种输入只有一个答案,但在实际工程中应该处理无解情况:
python复制def twoSum(nums, target):
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 [] # 或者抛出异常
题目说明不能使用相同元素两次,但数组可能有重复值。哈希表解法天然避免了这个问题,因为后出现的重复值会覆盖之前的记录,但不会与自己匹配。
这是两数之和的进阶版,要求找出数组中三个数之和为target的组合。这类问题通常需要结合哈希表和双指针技巧:
python复制def threeSum(nums):
nums.sort()
result = []
for i in range(len(nums)-2):
if i > 0 and nums[i] == nums[i-1]:
continue
left, right = i+1, len(nums)-1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s < 0:
left += 1
elif s > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
return result
当输入数组已经排序时,可以使用双指针法获得O(n)时间复杂度和O(1)空间复杂度的解法:
python复制def twoSumSorted(numbers, target):
left, right = 0, len(numbers)-1
while left < right:
s = numbers[left] + numbers[right]
if s == target:
return [left+1, right+1] # 题目要求索引从1开始
elif s < target:
left += 1
else:
right -= 1
两数之和算法在实际开发中有广泛的应用场景:
我在开发电商平台的优惠券系统时,就曾使用类似的算法来快速匹配满足优惠条件的商品组合。当用户有"满300减50"的优惠券时,系统需要快速找出购物车中哪些商品组合满足条件,这时优化后的两数之和算法就派上了用场。
在知道数组大小的情况下,可以预先分配哈希表空间,避免动态扩容带来的性能损耗:
python复制def twoSum(nums, target):
hash_map = {}
# 预分配空间(Python中效果有限,但在其他语言如Java中很有效)
hash_map.reserve(len(nums))
for i, num in enumerate(nums):
complement = target - num
if complement in hash_map:
return [hash_map[complement], i]
hash_map[num] = i
对于特别大的数组,可以考虑将数组分割后并行处理:
python复制from multiprocessing import Pool
def twoSumParallel(nums, target, chunk_size=10000):
def process_chunk(chunk):
local_map = {}
for i, num in chunk:
complement = target - num
if complement in local_map:
return (local_map[complement], i)
local_map[num] = i
return None
with Pool() as pool:
# 将数组分块处理
chunks = [(i, num) for i, num in enumerate(nums)]
results = pool.map(process_chunk, [chunks[i:i+chunk_size] for i in range(0, len(chunks), chunk_size)])
for res in results:
if res is not None:
return res
return []
java复制import java.util.HashMap;
import java.util.Map;
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);
}
throw new IllegalArgumentException("No two sum solution");
}
cpp复制#include <vector>
#include <unordered_map>
std::vector<int> twoSum(std::vector<int>& nums, int target) {
std::unordered_map<int, int> map;
for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if (map.find(complement) != map.end()) {
return {map[complement], i};
}
map[nums[i]] = i;
}
return {};
}
javascript复制function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}
我在第一次实现这个算法时,就曾因为忽略了重复元素的情况而导致提交失败。后来通过添加打印语句跟踪哈希表状态,才发现了问题所在。
使用Python的timeit模块对两种解法进行测试(n=10⁴):
python复制import timeit
setup = """
def twoSumBrute(nums, target):
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
def twoSumHash(nums, target):
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
nums = list(range(10000))
target = 19997 # 最后两个元素之和
"""
print("暴力解法:", timeit.timeit('twoSumBrute(nums, target)', setup=setup, number=10))
print("哈希表解法:", timeit.timeit('twoSumHash(nums, target)', setup=setup, number=10))
测试结果:
我在准备面试时,曾用一个月时间专门练习哈希表相关的题目,从两数之和开始,逐步攻克了LeetCode上所有哈希表标签的题目。这种针对性训练让我的算法能力得到了显著提升。