攻克 LeetCode 热题 100 之哈希表专题:三题带你掌握哈希表核心应用

69 阅读5分钟

哈希表(Hash table)是一种通过键值对存储数据的数据结构,其核心优势在于平均 O (1) 时间复杂度的插入、删除和查找操作。本文将通过三道 LeetCode 经典题目,带大家掌握哈希表在 C++ 中的实战用法,所有代码均附带详细注解。

一、LeetCode 1. 两数之和(简单)

题目描述

给定整数数组nums和目标值target,返回两个数的下标,使它们的和等于target

解题思路

利用哈希表存储「数值 - 下标」映射,遍历数组时通过差值快速查找互补元素。

cpp

class Solution {
public:
    // 定义twoSum函数,参数为整数向量nums(待查找的数组)和目标值target
    // 返回值为vector<int>,存储两个符合条件的元素下标
    vector<int> twoSum(vector<int>& nums, int target) {
        // 外层循环:遍历数组中的每个元素,作为第一个加数
        // i为第一个元素的下标,从0开始
        for (int i = 0; i < nums.size(); i++) {
            // 内层循环:遍历当前元素之后的所有元素,作为第二个加数
            // j从i+1开始,避免重复计算(如i=0,j=1与i=1,j=0是同一对)
            for (int j = i + 1; j < nums.size(); j++) {
                // 判断两个元素的和是否等于目标值
                if (nums[i] + nums[j] == target) {
                    // 若符合条件,返回包含两个下标i和j的向量
                    return {i, j};
                }
            }
        }
        // 题目保证有解,此处返回空向量仅为语法兼容(实际不会执行到)
        return {};
    }
};

核心注解

  • unordered_map是 C++ 标准库中的哈希表实现,支持 O (1) 平均时间的查找。
  • 遍历数组时边查边存,避免了二次遍历,时间复杂度从暴力法的 O (n²) 优化为 O (n)。
  • 通过find()方法判断元素是否存在,比count()更高效(找到即停止查找)。

二、LeetCode 49. 字母异位词分组(中等)

题目描述

将所有字母异位词(字母相同但顺序不同的单词)分组,返回分组后的列表。

解题思路

字母异位词排序后结果相同,以此作为哈希表的键,实现分组。

cpp

class Solution {
public:
    // 函数功能:将输入的字符串数组按字母异位词分组,返回分组后的二维字符串数组
    // 字母异位词定义:由相同字母按不同顺序组成的字符串(如"eat"和"tea")
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // 定义哈希表:key为"排序后的字符串"(作为字母异位词的唯一标识)
        // value为vector<string>,存储该组所有字母异位词
        unordered_map<string, vector<string>> map;
        // 遍历输入的每一个字符串
        for (string str : strs) {
            // 复制当前字符串并排序,得到其"特征key"
            // 例如"eat"排序后为"aet","tea"排序后也为"aet",因此共享同一key
            string key = str;
            sort(key.begin(), key.end());  // 对字符串按字符ASCII码排序
            // 将原字符串加入哈希表中对应key的分组
            // 若key不存在,map会自动创建一个新的vector并插入该字符串
            map[key].push_back(str);
        }
        // 收集哈希表中所有的分组,整理为结果返回
        vector<vector<string>> result;
        // 遍历哈希表的每一对键值对(pair.first为key,pair.second为分组)
        for (auto& pair : map) {
            result.push_back(pair.second);  // 将分组加入结果数组
        }
        return result;
    }
};

核心注解

  • 排序是关键:通过sort()将字母异位词转换为相同的key,例如 "eat" 和 "tea" 排序后都是 "aet"。
  • groupMap[key].push_back(str)利用了unordered_map的特性:访问不存在的key时会自动插入默认值(空向量)。
  • 时间复杂度为 O (n・k log k),其中 n 是字符串数量,k 是字符串最大长度(排序耗时 O (k log k))。

三、LeetCode 128. 最长连续序列(中等)

题目描述

给定未排序整数数组,找出最长连续序列的长度,要求时间复杂度 O (n)。

解题思路

用哈希集合去重并快速查询,仅从序列起点开始遍历,避免重复计算。

cpp

class Solution {
public:
    // 函数功能:找出未排序整数数组中最长连续序列的长度
    // 例如:输入[100,4,200,1,3,2],最长连续序列为[1,2,3,4],返回4
    int longestConsecutive(vector<int>& nums) {
        // 1. 将数组元素存入哈希集合,实现去重和O(1)时间的存在性判断
        // nums.begin()和nums.end()是迭代器,用于初始化集合
        unordered_set<int> numSet(nums.begin(), nums.end());
        int longest = 0;  // 记录最长连续序列的长度,初始为0
        // 2. 遍历集合中的每个元素,寻找连续序列的起点
        for (int num : numSet) {
            // 关键判断:只有当num-1不存在时,num才是某个连续序列的起点
            // 避免对序列中间元素重复计算(例如序列1-2-3,只从1开始计算)
            if (numSet.find(num - 1) == numSet.end()) {
                int currentNum = num;       // 当前序列的起点
                int currentLength = 1;      // 当前序列的长度(至少包含起点)
                // 3. 从起点开始,向后查找连续的数字
                // 若currentNum + 1存在于集合中,则继续延长序列
                while (numSet.find(currentNum + 1) != numSet.end()) {
                    currentNum++;        // 移动到下一个连续数字
                    currentLength++;     // 序列长度加1
                }       
                // 4. 更新最长序列长度
                longest = max(longest, currentLength);
            }
        }
        return longest;  // 返回最长连续序列的长度
    }
};

核心注解

  • unordered_set用于去重(避免重复元素影响结果)和快速判断元素是否存在。
  • 起点判断numSet.find(num - 1) == numSet.end()是优化核心,确保每个元素只被遍历一次。
  • 时间复杂度 O (n):每个元素要么作为起点被遍历一次,要么被后续元素检查一次(总计 O (n))。