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

LeetCode 49. 字母异位词分组:两种高效 C++ 解法

LeetCode 49 字母异位词分组提供两种 C++ 解法。解法一通过排序字符串生成规范键,代码直观但耗时 O(N·K log K)。解法二利用质数乘积映射字符,基于唯一分解定理实现 O(N·K) 复杂度,需处理 unsigned long long 溢出并避开质数 2。综合来看,质数乘法理论效率最优,但实现细节要求更高。

星云发布于 2026/2/6更新于 2026/6/66.9K 浏览
LeetCode 49. 字母异位词分组:两种高效 C++ 解法

LeetCode 49. 字母异位词分组:两种高效的 C++ 解法

💡 解法一:排序作为哈希键 (经典方法)

核心思路

字母异位词的本质是字符和数量完全相同。因此,将任何一组字母异位词按字典序排序后,它们都会得到一个唯一的、规范化的字符串。我们利用这个规范化的字符串作为哈希表的键,将所有原始字符串归类到对应的值列表中。

C++ 代码实现
class Solution {
public:
    std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
        // 哈希表:键是排序后的规范字符串,值是原始字符串列表
        std::unordered_map<std::string, std::vector<std::string>> m1;
        for(const std::string& s : strs) {
            std::string temp = s;
            // 1. 生成键:对字符串进行排序
            std::sort(temp.begin(), temp.end());
            // 2. 归组:将原始字符串添加到对应键的值列表中
            m1[temp].push_back(s);
        }
        // 3. 收集结果
        std::vector<std::vector<std::string>> ans;
        // 使用结构化绑定 (C++17) 遍历 map
        for(auto const& [key, group] : m1) {
            ans.emplace_back(group);
        }
        return ans;
    }
};
复杂度分析
  • 时间复杂度: O(N * K log K)。
    • N 是输入字符串的数量。
    • K 是字符串的最大长度。
    • 主要开销在于对每个字符串进行排序(O(K log K))。
  • 空间复杂度: O(N * K)。用于存储哈希表和结果。

🔥 解法二:质数乘积作为哈希键 (优化方法)

核心思路

为了避免排序带来的 O(K log K) 时间开销,我们可以利用数学中的算术基本定理(唯一分解定理)。该定理指出:任何一个大于 1 的自然数都可以分解为有限个质数的乘积,并且这种分解是唯一的。

我们将 26 个小写字母 a 到 z 分别映射到一个唯一的质数。对于一个字符串,将其所有字符对应的质数相乘得到一个乘积。由于质数分解的唯一性,只有字母异位词才会得到相同的乘积。这个乘积作为哈希表的键,可以实现 O(K) 时间复杂度的键生成。

C++ 代码实现
class Solution_Optimized {
public:
    std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
        // 质数数组,映射 'a' 到 'z'。
        // **关键优化:避免使用质数 2。** (见下文分析)
        std::vector<int> primes = {3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103};
        // 使用 unsigned long long 作为键,以容纳更大的乘积
        std::unordered_map<unsigned long long, std::vector<std::string>> m1;
        for(const std::string& s : strs) {
            unsigned long long key = 1;
            for(char ch : s) {
                // 1. 生成键:将字符映射到质数并相乘
                // ch - 'a' 得到 0-25 的索引
                key *= primes[ch - 'a'];
            }
            // 2. 归组
            m1[key].emplace_back(s);
        }
        // 3. 收集结果
        std::vector<std::vector<std::string>> ans;
        for(auto const& [key, group] : m1) {
            ans.emplace_back(group);
        }
        return ans;
    }
};
🔍 技巧与溢出陷阱分析

这种方法虽然高效,但涉及到一个关键的实现细节:哈希值溢出。

1. 利用 unsigned long long 的溢出特性

当多个质数相乘的结果超出 unsigned long long 的最大表示范围(2^64 - 1)时,会发生环绕溢出。

  • 溢出后的结果: 结果会模 2^64。即 result = (P1 * P2 * ... * Pn) mod 2^64。
  • 为何可行? 尽管发生了溢出,由于乘法是可交换的(a × b = b × a),只要乘数集合(即字符集)相同,无论乘法顺序如何,最终得到的模 2^64 的结果依然是相同的。这保证了字母异位词的键值依然唯一。
2. 为何不能用质数 2?

这是这个方法中最巧妙且最容易出错的地方。

  • 计算机的乘 2 操作: 在计算机底层,乘 2 操作等同于左移一位 (<< 1) 并在末尾补 0。
  • 溢出后的问题: 当乘积不断累乘 2 并最终溢出unsigned long long 的范围时,结果在模 2^64 运算后,最高位会被丢弃。如果一个数不断乘以 2 导致其所有位都溢出,结果将变为 0。当连续乘以多个 2 后,结果可能会变成 0。一旦键值变为 0,后续任何字符对应的质数与 0 相乘,结果都将是 0。
  • 结论: 键值一旦归 0,就无法区分是哪个字母异位词组导致了 0。为了保证不同字母异位词组不会意外得到相同的 0 键值,我们必须避免使用质数 2,从而规避这种特殊的乘法溢出陷阱。因此,我们将质数数组的起始值设为 3。
复杂度分析
  • 时间复杂度: O(N * K)。
    • N 是输入字符串的数量。
    • K 是字符串的最大长度。
    • 生成键只需要遍历字符串,时间复杂度为 O(K)。
  • 空间复杂度: O(N * K)。与解法一相同。

结论

解法键的生成方式键的类型时间复杂度优点缺点
一排序std::stringO(N * K log K)代码简洁,思路直观效率略低
二质数乘积unsigned long longO(N * K)理论上效率最高需处理溢出和质数选择细节

目录

  1. LeetCode 49. 字母异位词分组:两种高效的 C++ 解法
  2. 💡 解法一:排序作为哈希键 (经典方法)
  3. 核心思路
  4. C++ 代码实现
  5. 复杂度分析
  6. 🔥 解法二:质数乘积作为哈希键 (优化方法)
  7. 核心思路
  8. C++ 代码实现
  9. 🔍 技巧与溢出陷阱分析
  10. 1. 利用 unsigned long long 的溢出特性
  11. 2. 为何不能用质数 2?
  12. 复杂度分析
  13. 结论
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Java 实现八位无重复 UUID 生成方案
  • C++ 基于正倒排索引的搜索引擎核心实现与代码详解
  • C++ 初步学习(需 C 语言基础)
  • MambaRefine-YOLO:一种用于无人机影像的双模态小目标检测器
  • Cursor 实战:Web 版背单词应用开发
  • 网络安全学习路线与职业发展指南
  • 2026 协作机器人十大品牌盘点
  • ClawX:OpenClaw 可视化桌面客户端入门指南
  • PyGMT Python 地理绘图完整指南
  • 论文和文章去 AI 痕迹技巧:如何让 AI 生成内容更具“人味儿”
  • Python 项目依赖管理:requirements.txt 与 pyproject.toml 规范
  • 组合数学入门:核心概念与 4 种求组合数方法
  • 前端面试核心知识点梳理:JavaScript、框架与网络协议
  • X-admin:基于 Layui 的轻量级前端后台管理框架
  • openKylin 通过 SSH+cpolar 实现远程内网穿透
  • 时序数据库选型指南:Apache IoTDB 国产开源技术实践
  • LLM 评估框架详解:Arthur Bench 实践指南
  • 《一本书读懂大模型》:中国电信研究院出版的综合指南
  • 2019 年 CSP-S 提高组初赛真题解析:取石子游戏
  • Seedance 2.0 双分支扩散变换器架构解析与工程实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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