leetcode hot 100 之哈希

1.

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> hashmap;
        for(int i = 0;i<nums.size();i++){
            int complete = target-nums[i];
            if(hashmap.find(complete)!=hashmap.end()){
                return {hashmap[complete],i};
            }
            hashmap[nums[i]]=i;

        } 
        return {};
    }
};
  1. 创建一个哈希表,key:数组元素值,value:该元素在数组中的索引 - 说明哈希表的用途和结构

  2. unordered_map<int,int> hashmap; - 实例化无序哈希表,键和值都是整数类型

  3. 遍历数组 - 标记循环开始的注释

  4. for(int i = 0; i < nums.size(); i++) - 从第一个元素遍历到最后一个元素

  5. 计算当前元素需要的"另一半"是多少 - 解释补数计算的目的

  6. int complete = target - nums[i]; - 计算与当前元素配对能达成目标值的数值 在哈希表中查找这个"另一半"是否已经出现过 - 说明查找操作的含义

  7. if(hashmap.find(complete) != hashmap.end()) - 条件判断:如果找到补数(且不是当前元素)

  8. 如果找到了,说明之前遍历过的某个元素 + 当前元素 = target - 解释找到匹配时的逻辑关系

  9. 返回之前元素的索引和当前元素的索引 - 说明返回值的顺序和含义

  10. return {hashmap[complete], i}; - 返回两个索引:先返回补数的索引,后返回当前元素的索引 如果没找到,将当前元素及其索引存入哈希表,供后续元素查找 - 解释存储操作的目的和时机

  11. hashmap[nums[i]] = i; - 将当前元素值作为键、当前索引作为值存入哈希表

  12. 理论上题目保证有解,这里返回空数组作为兜底 - 说明理论上不会执行到这行,只是编译器要求

2.

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>>hashmap;
        for(int i=0;i<strs.size();i++)
        {
            string s=strs[i];
            sort(s.begin(),s.end());
            hashmap[s].push_back(strs[i]);
        }
        vector<vector<string>> ans;
        for(auto it=hashmap.begin();it!=hashmap.end();it++){
            ans.emplace_back(it->second);
        }
        return ans;
    }
};

哈希表,不过这里哈希表特殊的是,这是一个string对应一个数组,key对应一个数组value;

ans也是二维的,存放每一个value对应的数组

LeetCode 49 字母异位词分组详解(排序 + 哈希一遍过)

这道题是哈希表的经典入门题,核心思想其实非常简单:把"长得一样"的字符串归为一类 ;但这里的"长得一样"不是字面一样,而是排序后一样

比如 "eat""tea""ate",排序之后都会变成 "aet";所以我们可以把 "aet" 作为一个"标识",所有排序后相同的字符串就归到同一组。

你的代码实现就是这个思路,而且是最主流写法。

先定义一个 unordered_map<string, vector<string>>,key 是排序后的字符串,value 是这一组的所有原字符串;然后遍历原数组,每拿到一个字符串,就复制一份出来排序,得到 key;接着把原字符串丢进对应的桶里;最后把 map 里的所有 value 收集起来,就是答案。

核心代码逻辑其实可以浓缩成一句话:
排序后的字符串作为 key,原字符串作为 value 进行分组。

你这段代码是完全正确的,我们稍微帮你拆一下关键点:

第一步,排序构造"特征值":

复制代码
string s = strs[i];
sort(s.begin(), s.end());

这里 s 就是当前字符串的"标准形态"。

第二步,哈希分组:

复制代码
hashmap[s].push_back(strs[i]);

如果这个 key 不存在,unordered_map 会自动创建一个空 vector,然后再 push。

第三步,收集答案:

cpp 复制代码
for(auto it = hashmap.begin(); it != hashmap.end(); it++){
    ans.emplace_back(it->second);
}

时间复杂度是 O(n * k log k),其中 n 是字符串个数,k 是单个字符串长度(排序的复杂度);空间复杂度是 O(nk)。

再补充一个进阶优化思路:其实可以不用排序,而是用"字符计数"作为 key,比如用一个长度为 26 的数组记录每个字母出现次数,然后拼成字符串作为 key,这样复杂度可以降到 O(nk),不过写起来稍微麻烦一点。

总结一下这题的套路:
分组问题 → 找"统一特征" → 用哈希表归类

而"排序作为特征"是最常见、最稳的做法,这一类题基本都是这个思路。

这题属于面试高频,建议记住这个模板,后面很多字符串题都会用到类似思想。

3.

cpp 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (nums.empty()) return 0;
        
        unordered_set<int> mp(nums.begin(), nums.end());
        int longest = 0;
        
        for (int num : mp) {
            // 只从起点开始统计
            if (!mp.count(num - 1)) {
                int current = num;
                int len = 1;
                
                while (mp.count(current + 1)) {
                    current++;
                    len++;
                }
                
                longest = max(longest, len);
            }
        }
        
        return longest;
    }
};

最长连续序列(哈希优化)深入理解:为什么一定要找"起点"

你这份代码已经是这道题的最优解写法 ,接下来关键不是"会写",而是要真正理解:为什么这样写是 O(n)


一、核心代码再看一遍

cpp 复制代码
unordered_set<int> mp(nums.begin(), nums.end());
int longest = 0;

for (int num : mp) {
    // 关键:只从起点开始
    if (!mp.count(num - 1)) {
        int current = num;
        int len = 1;

        while (mp.count(current + 1)) {
            current++;
            len++;
        }

        longest = max(longest, len);
    }
}

二、最关键的一行代码

复制代码
if (!mp.count(num - 1))

这句话的含义是:

只有当 num 是"连续序列的起点"时,才开始往后扩展


三、什么是"起点"

定义非常简单:

如果一个数 x 存在,但 x-1 不存在,那么它就是起点

举个例子:

复制代码
nums = [100, 4, 200, 1, 3, 2]

转成集合后:

复制代码
{1,2,3,4,100,200}

起点是:

  • 1(没有0)

  • 100(没有99)

  • 200(没有199)

而 2、3、4 都不是起点,因为它们前面有数。


四、为什么必须这样写(核心)

如果你不加起点判断,会发生什么?

例如:

复制代码
[1,2,3,4,5]

你会这样做:

  • 从 1 开始 → 扫一遍(长度 5)

  • 从 2 开始 → 又扫一遍(长度 4)

  • 从 3 开始 → 再扫一遍(长度 3)

  • ...

总复杂度:

复制代码
O(n²)

五、加了"起点判断"之后

现在只会:

  • 从 1 开始扫一次

  • 2、3、4、5 全部跳过

每个元素最多被访问两次:

  • 一次用于判断是不是起点

  • 一次在 while 中被访问

所以总复杂度:

复制代码
O(n)

六、这题真正的思维提升

这题最重要的不是代码,而是这个思想:

避免重复计算 → 只从"关键点"出发

这是一类非常重要的优化套路,在很多题里都会出现,比如:

  • 区间问题

  • 图遍历优化

  • 哈希剪枝


七、一句话总结

连续问题 + O(n) 要求 = 哈希 + 起点扩展


如果你能完全理解"为什么只从起点开始",那这题你已经不是会做,而是吃透了

相关推荐
秋天的一阵风2 小时前
【LeetCode 刷题系列|第 3 篇】详解大数相加:从模拟竖式到简洁写法的优化之路🔢
前端·算法·面试
qwehjk20082 小时前
分布式计算C++库
开发语言·c++·算法
m0_716765232 小时前
C++提高编程--仿函数、常用遍历算法(for_each、transform)详解
java·开发语言·c++·经验分享·算法·青少年编程·visual studio
_深海凉_2 小时前
LeetCode热题100-反转链表
python·leetcode·链表
寻寻觅觅☆2 小时前
东华OJ-基础题-59-倒数数列(C++)
开发语言·c++·算法
我不是懒洋洋2 小时前
【数据结构】顺序表专题(详细代码及配图)
c语言·开发语言·数据结构·算法·青少年编程·visual studio
Trouvaille ~2 小时前
【优选算法篇】BFS 解决最短路——寻找最优路径的真谛
c++·算法·leetcode·面试·蓝桥杯·宽度优先·最短路问题
雅俗共赏1002 小时前
医学图像重建中常用的迭代求解器分类
图像处理·算法
不熬夜的熬润之2 小时前
KCF算法解析
人工智能·算法·计算机视觉·机器人