LeetCode 1. 两数之和 | 从暴力到哈希表的优化之路

作为 LeetCode 的第一道入门题,「两数之和」虽然标注为简单 难度,却是理解数组遍历与哈希表核心思想的经典案例。它不仅是算法新手的入门必修课,更能让我们体会到 空间换时间 的优化思维。今天从最基础的暴力解法开始,一步步拆解到时间复杂度 O (n) 的最优解法,带你彻底搞懂这道题的解题逻辑~

📖 题目重述

给你一个整数数组 nums 和一个目标值 target,请在数组中找出两个不同位置 的整数,使它们的和恰好等于 target,最后返回这两个数的下标。这里有几个关键规则要注意:

  1. 每个输入都只有一个正确答案
  2. 数组中的同一个元素不能被重复使用;
  3. 返回的下标顺序没有要求。

举个例子,输入数组 [2,7,11,15]、目标值 9,因为 2+7=9,所以返回下标 [0,1] 即可。

🚶 阶梯思路拆解

第一步:暴力枚举所有可能 🥾

要找两个数的和等于目标值,最直观的想法就是把数组中所有数两两配对,逐一检查和是否符合要求。这就是暴力解法的核心逻辑,也是最容易想到的思路。

💡 核心逻辑

用两层嵌套的 for 循环遍历数组:

  • 外层循环固定一个数 nums[i],遍历范围是数组的每一个元素;
  • 内层循环遍历 i 之后的所有数 nums[j](避免重复检查同一对数字);
  • nums[i] + nums[j] == target,直接返回下标数组 {i, j}

✅ 代码实现(Java)

ini 复制代码
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        // 外层循环固定第一个数
        for (int i = 0; i < n; i++) {
            // 内层循环遍历第二个数,从i+1开始避免重复
            for (int j = i + 1; j < n; j++) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        // 题目保证有解,此处仅为语法兜底
        return new int[0];
    }
}

// 测试代码
public class Main {
    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums1 = {2,7,11,15};
        int[] res1 = solution.twoSum(nums1, 9);
        System.out.println(res1[0] + "," + res1[1]); // 输出0,1

        int[] nums2 = {3,2,4};
        int[] res2 = solution.twoSum(nums2, 6);
        System.out.println(res2[0] + "," + res2[1]); // 输出1,2
    }
}

⚙️ 复杂度分析

复杂度类型 计算结果 说明
时间复杂度 O(n²) 两层嵌套循环,最坏情况下需要遍历 n*(n-1)/2 次,n 为数组长度
空间复杂度 O(1) 仅使用了几个临时变量,未开辟额外的数组或集合空间

🚫 遇到的问题

暴力解法的逻辑简单,但效率极低。当数组长度 n 达到 10⁴ 时,n² 就是 10⁸ 次运算,会触发 LeetCode 的超时判定;如果是百万级别的数组,这种方法更是完全不可行。问题的核心在于:内层循环的查找过程重复且耗时,我们需要找到一种更高效的查找方式。

第二步:用哈希表优化查找效率 🗺️

既然暴力解法的痛点是 查找补数 的过程太慢,那我们可以用 哈希表(HashMap)来记录已经遍历过的数字和它的下标,把查找操作从 O (n) 降到 O (1)。这是算法优化中典型的 空间换时间 策略。

💡 核心逻辑

  1. 初始化一个 HashMap,键存储遍历过的数字 ,值存储该数字对应的数组下标
  2. 遍历数组时,对当前数字 nums[i],计算它的 补数 complement = target - nums[i]
  3. 检查补数是否存在于 HashMap 中:
    • 若存在,说明之前已经遍历过这个补数,直接返回 {map.get(complement), i}
    • 若不存在,将当前数字和下标存入 HashMap,继续遍历下一个元素。

📊 图文演示(以 nums=[2,7,11,15], target=9 为例)

我们一步步拆解 HashMap 的变化和查找过程,直观感受优化后的逻辑(如图所示):

  1. 遍历 i=0,nums [i]=2,补数 = 9-2=7。此时 HashMap 为空,将 2→0 存入 Map:

    ini 复制代码
    HashMap:{2=0}
    当前遍历:nums[0]=2 → 补数7不存在 ❌
  2. 遍历 i=1,nums [i]=7,补数 = 9-7=2。检查 HashMap,发现 2 存在且对应下标 0:

    ini 复制代码
    HashMap:{2=0}
    当前遍历:nums[1]=7 → 补数2存在 ✅ → 返回{0,1}

✅ 代码实现(Java)

ini 复制代码
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 初始化HashMap,键:数字,值:下标
        Map<Integer, Integer> numMap = new HashMap<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int complement = target - nums[i];
            // 检查补数是否在HashMap中
            if (numMap.containsKey(complement)) {
                return new int[]{numMap.get(complement), i};
            }
            // 若不存在,将当前数字和下标存入Map
            numMap.put(nums[i], i);
        }
        // 题目保证有解,此处仅为语法兜底
        return new int[0];
    }
}

// 测试代码
public class Main {
    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums1 = {2,7,11,15};
        int[] res1 = solution.twoSum(nums1, 9);
        System.out.println(res1[0] + "," + res1[1]); // 输出0,1

        int[] nums2 = {3,3};
        int[] res2 = solution.twoSum(nums2, 6);
        System.out.println(res2[0] + "," + res2[1]); // 输出0,1
    }
}

⚙️ 复杂度分析

复杂度类型 计算结果 说明
时间复杂度 O(n) 仅需遍历一次数组,HashMap 的查找和插入操作均为 O (1)
空间复杂度 O(n) 最坏情况下,需要将数组中 n 个元素全部存入 HashMap

✨ 优化亮点

这种方法通过 HashMap 将原本 O (n) 的查找过程优化为 O (1),时间复杂度从 O (n²) 骤降到 O (n),虽然牺牲了部分空间,但在实际开发中,时间效率的提升往往更重要。这也是「两数之和」这道题的最优解法

📝 总结

「两数之和」看似简单,却蕴含了算法优化的核心思维:用合适的数据结构解决性能瓶颈 。我们从暴力枚举的基础思路出发,找到 重复查找 的性能问题,再通过哈希表实现高效查找,这个优化路径也是很多数组类算法题的通用解题模板。

同类题扩展建议

掌握了这道题的哈希表思路后,可以继续练习这些进阶题目,巩固优化思维:

  1. LeetCode 15. 三数之和:在两数之和的基础上增加一层循环,结合双指针优化去重;
  2. LeetCode 18. 四数之和:延续三数之和的思路,注意边界条件和去重逻辑;
  3. LeetCode 454. 四数相加 II:用两个 HashMap 分别存储两组数的和,进一步降低时间复杂度。

算法学习不是一蹴而就的,从基础解法开始逐步优化,才能真正理解背后的逻辑。希望这篇文章能帮你吃透「两数之和」,也为后续的算法学习打下坚实的基础~

📢 关注不迷路,算法学习更高效!

如果这篇拆解对你有帮助,别忘了关注我的微信公众号【小镇冥想人】 ~ 后续会持续更新 LeetCode 高频题的阶梯式解题思路,从暴力到最优解层层递进,每道题都搭配详细图文和代码注释,帮你轻松攻克算法难关。

相关推荐
水力魔方16 分钟前
武理排水管网模拟分析系统应用专题5:模型克隆与并行计算
数据库·c++·算法·swmm
谈笑也风生1 小时前
经典算法题型之排序算法(三)
java·算法·排序算法
大佬,救命!!!2 小时前
对算子shape相关的属性值自动化处理
python·算法·自动化·学习笔记·算子·用例脚本·算子形状
高山上有一只小老虎2 小时前
小红的推荐系统
java·算法
冰西瓜6002 小时前
贪心(一)——从动态规划到贪心 算法设计与分析 国科大
算法·贪心算法·动态规划
javachen__2 小时前
341-十道经典程序设计题目
数据结构·c++·算法
natide3 小时前
表示/嵌入差异-7-间隔/边际对齐(Alignment Margin)
人工智能·深度学习·算法·机器学习·自然语言处理·知识图谱
毅炼3 小时前
hot100打卡——day08
java·数据结构·算法·leetcode·深度优先
l1t3 小时前
DeepSeek总结的算法 X 与舞蹈链文章
前端·javascript·算法
gihigo19983 小时前
水声信号处理中DEMON谱分析的原理、实现与改进
算法·信号处理