跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

哈希表、Set 与 Map 基础算法总结

总结了哈希表、Set 与 Map 在 C++ 算法中的应用。内容涵盖两数之和、数组交集、字符重排、重复元素检测、前 K 个高频单词及字母异位词分组等经典题目。文章详细讲解了哈希映射原理、STL 容器(unordered_map, unordered_set, set)的使用方法、时间复杂度优化技巧以及稳定排序在特定场景下的应用,并提供了完整的 C++ 代码实现。

GopherDev发布于 2026/3/21更新于 2026/6/1936 浏览
哈希表、Set 与 Map 基础算法总结

一,哈希表简介

哈希思想是算法中一个十分重要的思想,体现的是一种映射关系,而哈希表就是基于哈希思想实现的存储数据的容器。哈希表的作用是快速查找某个元素,时间复杂度为 O(1),遍历数组的时间复杂度为 O(N)。

使用哈希一般有两种方式: (1) STL 中的 unordered 系列容器。 (2) 用数组模拟简易哈希表。这种情况一般用于处理字符串中的'字符'或是当数据范围很小的时候使用。

下面将通过若干道题目进一步体会哈希表的使用。

二,算法原理和代码实现

1. 两数之和

算法原理:

(1) 如果我们可以事先将数组内的元素和下标绑定在一起存入哈希表中,然后直接在哈希表中查找每一个元素的 target - nums[i],就能快速的找到目标和的下标。 (2) 这里有一个小技巧,我们不用将元素全部放入到哈希表之后,再来二次遍历 (因为要处理元素相同的情况)。而是在将元素放入到哈希表中的同时,直接来检查表中是否已经存在当前元素所对应的目标元素 (即 target - nums[i])。如果它存在,那我们已经找到了对应解,并立即将其返回。无需将元素全部放入哈希表中,提高效率。 (3) 因为哈希表中查找元素的时间复杂度是 O(1),遍历一遍数组的时间复杂度为 O(N),因此可以将时间复杂度降到 O(N)。

代码实现:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash; // 存元素和对应的下标
        for (int i = 0; i < nums.size(); i++) {
            // 检查在该元素之前是否存在一个数等于 target-nums[i]
            int x = target - nums[i];
            if (hash.count(x)) return {hash[x], i};
            hash[nums[i]] = i; // 不存在就插入
        }
        return {-1, -1};
    }
};

349. 两个数组的交集

算法原理:

解法 1:使用哈希表。 定义两个哈希表 hash1 和 hash2,把两个数组都扔进哈希表中进行去重,遍历 hash1 中的每个元素,在 hash2 中查找是否存在,若存在就是交集。把交集存入数组里即可。

代码实现:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // 去重
        unordered_set<int> s1(nums1.begin(), nums1.end());
        unordered_set<int> s2(nums2.begin(), nums2.end());
        // 遍历 s1,在 s2 中查找是否存在,若存在就是交集
        vector<int> ret;
        for (auto x : s1)
            if (s2.find(x) != s2.end()) ret.push_back(x);
        return ret;
    }
};

解法 2:使用 set 排序 + 去重。 这种解法不仅能找出交集,也能找到差集。 把两个数组都扔进 set 容器达到排序 + 去重的效果,再依次比较两个 set 里的元素,谁小谁++,相等时再同时++,此时相等的元素就是交集,较小的元素就是差集,直到其中一个 set 走完了,另一个 set 剩下的元素也是差集。

代码实现:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // 排序 + 去重
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());
        // 依次比较,谁小谁++,相等的就是交集,再同时++
        auto it1 = s1.begin(), it2 = s2.begin();
        vector<int> ret;
        while (it1 != s1.end() && it2 != s2.end()) {
            if (*it1 < *it2) ++it1;
            else if (*it1 > *it2) ++it2;
            else ret.push_back(*it1), ++it1, ++it2;
        }
        return ret;
    }
};

面试题 01.02. 判断是否互为字符重排

算法原理:

(1) 当两个字符串的长度不相等的时候,是不可能构成互相重排的,直接返回 false。 (2) 如果两个字符串能够构成互相重排,那么每个字符串中各个字符出现的次数一定是相同的。因此,我们可以分别统计出这两个字符串中各个字符出现的次数,然后逐个比较是否相等即可。所以可以选择哈希表来统计字符串中字符出现的次数。

代码实现:

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        // 长度不相等,直接返回 false
        if (s1.size() != s2.size()) return false;
        int hash1[26] = {0}, hash2[26] = {0};
        // 先统计 s1 和 s2 中每个字符出现得到次数
        for (auto ch : s1) hash1[ch - 'a']++;
        for (auto ch : s2) hash2[ch - 'a']++;
        // 再比较两个哈希表中每个字符出现的次数是否相同
        for (char ch = 'a'; ch <= 'z'; ch++)
            if (hash1[ch - 'a'] != hash2[ch - 'a']) return false;
        return true;
    }
};

优化:只使用一个哈希表。 用一个哈希表先统计 s1 中每个字符出现的个数,再遍历 s2 时把 s2 中出现的字符在哈希表中 -1。如果构成重排列,则最后哈希表中的个数都为 0,否则不构成。

代码实现:

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        // 长度不相等,直接返回 false
        if (s1.size() != s2.size()) return false;
        int hash[26] = {0};
        // 统计 s1 中字符出现的次数
        for (auto ch : s1) hash[ch - 'a']++;
        // 再遍历 s2,出现的字符 -1
        for (auto ch : s2) hash[ch - 'a']--;
        // 最后检查 hash 里是否都为 0
        for (int i = 0; i < 26; i++)
            if (hash[i] != 0) return false;
        return true;
    }
};

217. 存在重复元素

算法原理:

这道题和第一题的做法基本类似。

分析一下题目,出现至少两次的意思就是数组中存在着重复的元素,因此我们可以无需统计元素出现的数目。仅需在遍历数组的过程中,检查当前元素是否在之前已经出现过即可。 因此我们可以利用哈希表,仅需存储数组内的元素。在遍历数组的时候,一边检查哈希表中是否已经出现过当前元素,一边将元素加入到哈希表中。

代码实现:

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> hash;
        for (auto x : nums)
            if (hash.count(x)) return true;
            else hash.insert(x);
        return false;
    }
};

219. 存在重复元素 II

算法原理:

这道题和上一题基本一样,只不过除了要两个元素相等之外,还需要判断相等元素的下标差的绝对值是否小于等于 k。所以本题的哈希表要把元素值和对应下标绑定。

细节问题:

如果数组内存在大量的重复元素,而我们判断下标差的绝对值不符合要求时,在插入相同元素的下标,会覆盖掉原来的下标,那结果还正确吗,怎么处理? 结果依然是正确的,可以大胆的覆盖掉!

原因: 我们按照下标从小到大的顺序遍历数组,当遇到两个元素相同,并且比较它们的下标时,这两个下标一定是距离最近的,因为: (1) 如果当前判断符合条件直接返回 true,无需继续往后查找。 (2) 如果不符合条件,那么前一个下标一定不可能与后续相同元素的下标匹配 (因为下标在逐渐变大),那么我们可以大胆舍去前一个存储的下标,转而将其换成新的下标,继续匹配。

代码实现:

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int, int> hash; // 绑定元素和下标
        for (int i = 0; i < nums.size(); i++) {
            if (hash.count(nums[i])) {
                if (abs(hash[nums[i]] - i) <= k) return true;
            }
            hash[nums[i]] = i;
        }
        return false;
    }
};

692. 前 k 个高频单词

算法原理:

解法:使用 map + 稳定排序函数。 先用 map 统计出每个单词出现的次数,map 中是按照单词的字典序排序的,所以还需要用排序函数对次数进行排降序,最后提取出前 k 个出现最多的字符串。

魔鬼细节问题:

对次数再排序时有以下几个注意点: (1) 排序函数一定要使用稳定排序!算法库中的 sort 是不稳定的,但是 stable_sort 是稳定的。 因为如果使用不稳定的排序,则出现次数相同的字符串顺序又会被打乱 (map 中已经是按单词的字典序排序的),不符合题意。 (2) 使用排序函数之前要把 map 中的元素先导入 vector 中,再传递 vector 的迭代器给 stable_sort。 因为 stable_sort 只能传递随机迭代器,而 map 的迭代器是双向迭代器,vector 的迭代器是随机迭代器。 (3) stable_sort 的第三个参数:一个可调用的比较函数和函数对象。

代码实现:

class Solution {
struct kvcmp {
    bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) {
        return kv1.second > kv2.second;
    }
};
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        // 统计每个字符串出现的次数
        map<string, int> mapCnt;
        for (auto& s : words) mapCnt[s]++;
        vector<pair<string, int>> v(mapCnt.begin(), mapCnt.end());
        // 要用稳定排序对字符串出现的次数排降序
        stable_sort(v.begin(), v.end(), kvcmp());
        // 提取出前 k 个出现次数最多的单词
        vector<string> ret;
        for (int i = 0; i < k; i++) ret.push_back(v[i].first);
        return ret;
    }
};

45. 字母异位词分组

算法原理:

这道题要解决两个问题: (1) 判断两个字符串是否是字母异位词。 我们可以使用上面题目中的做法用哈希表统计字符出现的个数再进行判断。这里用另一种更简洁的方法,排序,但是时间复杂度更高。把每个字符串按字典序进行排序,若是字母异位词,则排完序后字符串相等,否则不相等。 (2) 如何把字母异位词分组。 我们可以这样定义哈希表,把 key 定义为 string,把 value 定义为字符串数组 vector。 把每个字符串排序后再去哈希表中查找,如果存在,就把它绑定到对应的字符串数组中。

代码实现:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> hash; // 把所有的字母异位词分组
        for (auto& s : strs) {
            string tmp = s;
            sort(tmp.begin(), tmp.end());
            hash[tmp].push_back(s);
        }
        // 提取结果
        vector<vector<string>> ret;
        for (auto& [x, y]: hash) ret.push_back(y);
        return ret;
    }
};

三,算法总结

通过上面的若干道题目可以发现,哈希容器/map/set 是非常强大的,能够快速的查找数据是否存在,统计数据个数,去重,排序。

目录

  1. 一,哈希表简介
  2. 二,算法原理和代码实现
  3. 1. 两数之和
  4. 349. 两个数组的交集
  5. 面试题 01.02. 判断是否互为字符重排
  6. 217. 存在重复元素
  7. 219. 存在重复元素 II
  8. 692. 前 k 个高频单词
  9. 45. 字母异位词分组
  10. 三,算法总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 文心 4.5 系列大模型本地化部署与多模型深度测评
  • CCF GESP C++ 8 级编程能力认证试题
  • 智能小车快速循迹串级 PID 算法实现
  • Coze 工作流全面解析与 AI Agent 实例搭建
  • 基于遗传算法的无人机烟幕遮蔽时间优化
  • Telegram搜索机器人推荐——查找海量资源,提升信息检索效率
  • Linux 下 Vim 编辑器使用详解
  • 使用 Frontend-Design Skill 增强大模型前端设计能力
  • 飞书机器人联动 Claude Code:打造移动端 AI 编程助手
  • 前端三年复盘:从迷茫摸索到互联网工程实战
  • 循环神经网络(RNN)与序列数据处理实战
  • ComfyUI 本地部署及 Stable Diffusion 使用指南
  • 基于 OpenClaw 与飞书集成构建 AI 新闻推送机器人
  • Wan2.2 视频生成风格单一?LoRA 微调实战提升多样性
  • OpenClaw 跨平台安装教程:Windows、macOS、Linux
  • SuperMerger 模型融合实战:权重控制与 MBW 详解
  • Claude Agent SDK Python 技术文档
  • 普通产品经理转型 AI 产品经理:必备准备与技能提升
  • 前端三年成长记:从理想主义到工程实战的蜕变
  • 大型语言模型解决组合问题研究:旅行商问题案例

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online