两数之和:从暴力破解到哈希表优化,一文搞懂经典算法题!

81 阅读4分钟

在 LeetCode 上, “两数之和”(Two Sum) 是最经典的入门算法题之一。它看似简单,却蕴含了重要的编程思想——用空间换时间。今天,我就结合三张代码图,带你深入剖析这道题的三种解法,并揭示背后的核心逻辑。


🌟 问题描述

给定一个整数数组 nums 和一个目标值 target,找出数组中两个数的索引,使得它们的和等于 target

示例:

nums = [2, 7, 11, 15], target = 9
输出: [0, 1] 因为 nums[0] + nums[1] = 2 + 7 = 9

❌ 方法一:暴力破解 —— O(n²) 时间复杂度

最直观的想法是:遍历每一个元素,再遍历其后的所有元素,看是否有满足条件的组合。

function twoSum(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        return [i, j];
      }
    }
  }
}

🔍 分析:

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 缺点:效率低,数据量大时性能差。

💡 这种方法虽然能解决问题,但显然不是最优解。


✅ 方法二:哈希表优化 —— O(n) 时间复杂度(ES5 版本)

我们换个思路:把“找和”变成“找差”

核心思想:

对于当前元素 nums[i],我们只需要知道 target - nums[i] 是否已经出现在前面的元素中。

如果存在,就找到了答案;否则,将当前元素存入哈希表,继续遍历。

function twoSum(nums, target) {
  const diffs = {}; // 使用对象模拟 HashMap
  const len = nums.length;

  for (let i = 0; i < len; i++) {
    const complement = target - nums[i]; // 求差

    if (diffs[complement] !== undefined) {
      return [diffs[complement], i];
    }

    diffs[nums[i]] = i; // 存储值 -> 索引
  }
}

🔍 原理解析:

  1. 遍历数组,对每个元素计算 target - nums[i]
  2. 查看这个“差值”是否已经在 diffs 中出现过。
  3. 如果有,说明之前某个元素与当前元素相加等于 target
  4. 否则,将当前元素及其索引存入 diffs

💡 优点:

  • 时间复杂度:O(n),只遍历一次。
  • 空间复杂度:O(n),用于存储哈希表。
  • 实现简洁,适合生产环境。

⚠️ 注意:这里使用的是普通对象 {} 模拟哈希表,适用于 ES5 环境。


✅ 方法三:哈希表优化 —— O(n) 时间复杂度(ES6 Map 版本)

ES6 提供了原生的 Map 数据结构,更适合做键值映射。

function twoSum(nums, target) {
  const diffs = new Map(); // 使用 Map,支持任意类型 key
  const len = nums.length;

  for (let i = 0; i < len; i++) {
    const complement = target - nums[i];

    if (diffs.has(complement)) {
      return [diffs.get(complement), i];
    }

    diffs.set(nums[i], i);
  }
}

🔍 对比 Object vs Map

特性ObjectMap
Key 类型字符串/符号任意类型(包括对象)
性能一般更快
可迭代需要手动处理直接可迭代
易读性代码更短方法名清晰(has/get/set)

✅ 推荐在现代项目中使用 Map,更安全、更高效。


📊 算法对比总结

方法时间复杂度空间复杂度是否推荐
暴力破解O(n²)O(1)❌ 不推荐
哈希表(Object)O(n)O(n)✅ 推荐
哈希表(Map)O(n)O(n)✅✅ 强烈推荐

💡 核心思想提炼

这张图总结得非常好:

“求和变成求差” + “用空间换时间”

  • 求和变求差:不要每次都去算 a + b == target,而是先算出 target - a,然后查有没有这个值。
  • 用空间换时间:通过哈希表存储已访问过的元素,实现 O(1) 查询,从而将整体时间复杂度降到 O(n)。

这就是经典的 “哈希表优化” 思想,也是后续很多算法题的基础。


🚀 进阶思考

1. 能否处理重复元素?

可以!只要保证返回的是正确的索引即可。

2. 数组中有多个解怎么办?

题目通常假设只有一个解,但如果允许多个,可以收集所有结果。

3. 如何处理负数或浮点数?

MapObject 都支持,无需额外处理。


📌 总结

“两数之和”不仅仅是一道题,它是算法思维启蒙的第一课。它教会我们:

不要盲目暴力,要学会用数据结构优化算法。

从 O(n²) 到 O(n),从嵌套循环到哈希表,每一次优化都是一次思维的跃迁。

真正的高手,不是写得多快,而是想得多远。

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!