【算法日记 19】LeetCode 1. 两数之和:梦开始的地方,哈希表的降维打击

📍 题目背景

今天咱们来聊聊无数程序员刷题之路的起点,传说中的 LeetCode 第 1 题:两数之和 。 别看它是一道简单题,很多人一开始只会用两个 for 循环暴力破解(时间复杂度 O(N\^2))。而在大厂面试中,面试官真正想考察的是你能不能用空间换时间的思维,把它优化到极致的 O(N)

【题目描述】 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

【示例】

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

✨ 解法一:哈希表"备忘录" (终极满分解法 O(N))

【核心逻辑】 如果用暴力法,我们拿到一个数字 2,就得跑去后面挨个问:"你们谁是 7 呀?"这种做法极其浪费时间。 更聪明的做法是:我们准备一个"超级雷达备忘录"(哈希表 unordered_map)。

  1. 遍历到一个数字(比如 2),我们心里立刻算出它需要的另一半 need = target - 2 = 7

  2. 我们翻开备忘录查一下:刚才有人登记过 7 吗?

  3. 如果查到了,直接配对成功,返回它俩的座位号(下标)。

  4. 如果没查到,就把当前的数字 2 和它的座位号登记在备忘录上,等待后面有缘人的到来。

💻 C++ 代码实现

复制代码
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 核心备忘录:记录格式为 <数字本身, 它的原始下标>
        unordered_map<int, int> mp;
        
        // 只需要从左到右看一遍数组!
        for (int i = 0; i < nums.size(); i++) {
            int need = target - nums[i]; // 算出命中注定的另一半
            
            // 去备忘录里查,这个另一半刚才出现过吗?
            if (mp.count(need)) {
                // 找到了!返回它俩的下标
                return {mp[need], i};
            }
            
            // 没找到另一半,就把自己和自己的下标存进备忘录,等待有缘人
            mp[nums[i]] = i;
        }
        
        return {}; // 兜底,没找到就返回空
    }
};

🚀 解法二:双指针法 + "贴名牌" (O(N \\log N) 进阶思路)

有些同学非常喜欢用"排序 + 左右指针向中间夹击"的方法来求两数之和。 但是,这道题有一个巨大的坑: 题目要求返回的是 "原始下标"!一旦你对数组使用了 sort() 排序,数字的座位就全乱了,原来的下标就彻底丢失了!

【补救绝招:贴名牌】 既然怕乱,我们在排序前,就用 C++ 的 pair 把每个数字和它的原始下标绑在一起(贴上名牌)。排完队后,我们看着名牌就能知道它原来坐在哪里!

💻 C++ 代码实现

复制代码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 1. 给每个数字贴上"原始座位号"的名牌
        vector<pair<int, int>> numsWithIndex;
        for (int i = 0; i < nums.size(); i++) {
            numsWithIndex.push_back({nums[i], i});
        }
        
        // 2. 带着名牌一起排序 (默认根据 pair 的 first 数字大小排序)
        sort(numsWithIndex.begin(), numsWithIndex.end());
        
        // 3. 派出左右小弟夹击
        int left = 0;
        int right = numsWithIndex.size() - 1;
        
        while (left < right) {
            int sum = numsWithIndex[left].first + numsWithIndex[right].first;
            
            if (sum == target) {
                // 找到啦!提取它们名牌上的"原始座位号"
                return {numsWithIndex[left].second, numsWithIndex[right].second}; 
            } 
            else if (sum < target) {
                left++;  // 太小了,左指针右移找大数
            } 
            else {
                right--; // 太大了,右指针左移找小数
            }
        }
        
        return {}; 
    }
};

🔍 总结与复杂度分析

  1. 哈希表法(推荐解法):

    • 时间复杂度: O(N)。只需遍历数组一次,哈希表的查询时间是 O(1)

    • 空间复杂度: O(N)。需要一个哈希表来存储遍历过的数字。

    • 点评: 典型的"空间换时间",是实际工程开发和算法面试中最完美的标准答案。

  2. 贴名牌双指针法:

    • 时间复杂度: O(N \\log N)。虽然寻找答案只需 O(N),但前期的 sort 排序拖慢了整体速度。

    • 空间复杂度: O(N)。需要额外开辟一个存 pair 的数组。

    • 点评: 虽然不是最优解,但这种"利用 Pair 绑定原始信息"的技巧非常巧妙,在很多更复杂的算法题(如寻找第K大的元素等)中经常用到,是非常棒的思维拓展!

相关推荐
CS创新实验室10 小时前
算法、齿轮与硅基大脑:数值计算发展简史
人工智能·算法·数值计算
海石12 小时前
1563分的简单题,可能就简单在能被暴力AC
算法·leetcode
海石12 小时前
1400分的dp汗流浃背之【交替子数组计数】
算法·leetcode
奋发向前wcx12 小时前
P2590 树的统计 题目解析
数据结构·算法·深度优先
imbackneverdie13 小时前
AI4S不止于分子药物:以MedPeer为代表的科研基建打开产业新增量
大数据·人工智能·算法·aigc·科研·学术·ai 4s
额鹅恶饿呃14 小时前
C语言中的数据结构和变量
c语言·数据结构·算法
运行时记录15 小时前
prompt-optimizer skill
算法
万法若空16 小时前
【数据结构-哈希表】哈希表原理
数据结构·算法·散列表
退休倒计时16 小时前
【每日一题】LeetCode 437. 路径总和 III TypeScript
算法·leetcode·typescript