leetcode 49字母异位分组

LeetCode 49. 字母异位词分组(哈希表 + 排序)

一、完整代码(精简注释)

复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // key:排序后的字符串,value:对应的字母异位词集合
        unordered_map<string, vector<string>> res;

        for (auto x : strs) {
            string key = x;
            sort(key.begin(), key.end()); // 排序后作为哈希表的 key

            res[key].emplace_back(x); // 将原字符串加入对应的 vector
        }

        vector<vector<string>> ans;

        // 遍历哈希表,将每组字母异位词存入结果
        for (auto [x, y] : res) {
            ans.emplace_back(y);
        }

        return ans;
    }
};

二、解题思路

本题要求将字符串数组中的字母异位词分到同一组。

字母异位词指的是:两个字符串包含相同的字符,且每个字符出现的次数相同,只是排列顺序不同。

例如:

复制代码
"eat"、"tea"、"ate"

这三个字符串排序后都是:

复制代码
"aet"

因此,可以利用排序后的字符串作为哈希表的 key,将所有具有相同 key 的字符串存储在同一个 vector<string> 中。

核心思想:字母异位词经过排序后,一定得到相同的字符串。

三、代码关键步骤

1. 创建哈希表

复制代码
unordered_map<string, vector<string>> res;

这里需要重点理解哈希表的结构。

  • key:排序后的字符串。

  • value:一个 vector<string>,存储所有对应的原始字符串。

例如:

复制代码
res["aet"] = {"eat", "tea", "ate"};
res["ant"] = {"tan", "nat"};
res["abt"] = {"bat"};

注意:哈希表中的 key 是唯一的,但每个 key 对应的 vector 可以存储多个字符串。

2. 遍历字符串数组

复制代码
for (auto x : strs) {
    string key = x;
    sort(key.begin(), key.end());
}

这里:

复制代码
string key = x;

相当于复制当前字符串。

例如:

复制代码
x = "eat";
key = "eat";

排序后:

复制代码
x = "eat";    // 原字符串不变
key = "aet";  // 排序后的字符串

为什么不直接排序 x?

因为题目要求返回原始字符串,而不是排序后的字符串。

3. 将字符串存入对应的 vector

复制代码
res[key].emplace_back(x);

这是本题最关键的一行代码。

可以分成两部分理解:

复制代码
res[key]

表示获取哈希表中 key 对应的 vector<string>。

如果 key 不存在,operator[] 会自动创建一个空的 vector<string>。

然后:

复制代码
.emplace_back(x)

将原始字符串 x 添加到该 vector 的末尾。

例如:

复制代码
res["aet"].emplace_back("eat");
res["aet"].emplace_back("tea");
res["aet"].emplace_back("ate");

最终得到:

复制代码
res["aet"] = {"eat", "tea", "ate"};

因为 key 相同,所以三个字符串都被添加到同一个 vector 中。

4. 将哈希表中的 value 存入答案

复制代码
vector<vector<string>> ans;

for (auto [x, y] : res) {
    ans.emplace_back(y);
}

这里的:

复制代码
auto [x, y] : res

是 C++17 的结构化绑定。

其中:

  • x:哈希表的 key,即排序后的字符串。

  • y:哈希表的 value,即对应的字符串数组。

例如:

复制代码
x = "aet";
y = {"eat", "tea", "ate"};

执行:

复制代码
ans.emplace_back(y);

相当于将整个 vector<string> 添加到二维数组 ans 中。

最终得到:

复制代码
ans = {
    {"eat", "tea", "ate"},
    {"tan", "nat"},
    {"bat"}
};

由于使用的是 unordered_map,所以最终各组之间的顺序不固定。

四、举例模拟

假设输入:

复制代码
strs = {"eat", "tea", "tan", "ate", "nat", "bat"};

遍历过程如下:

当前字符串 x 排序后的 key 哈希表操作
"eat" "aet" 创建分组,加入 "eat"
"tea" "aet" 加入已有分组
"tan" "ant" 创建新分组
"ate" "aet" 加入已有分组
"nat" "ant" 加入已有分组
"bat" "abt" 创建新分组

遍历结束后,哈希表的逻辑结构为:

key:"aet"

"eat", "tea", "ate"

key:"ant"

"tan", "nat"

key:"abt"

"bat"

最后遍历哈希表,只取出每个 key 对应的 value,组成二维数组。

结果为:

复制代码
[
    ["eat", "tea", "ate"],
    ["tan", "nat"],
    ["bat"]
]

五、复杂度分析

假设:

  • n 表示字符串数组中字符串的数量。

  • k 表示字符串的最大长度。

  1. 时间复杂度:O(n × k log k)

外层循环遍历 n 个字符串:

复制代码
for (auto x : strs)

每个字符串都需要排序:

复制代码
sort(key.begin(), key.end());

排序时间复杂度为 O(k log k)。

哈希表查找和插入的平均开销与字符串长度有关,通常为 O(k)。

因此,总的平均时间复杂度为:

\ O(nk\\log k) \\

  1. 空间复杂度:O(nk)

哈希表需要保存排序后的 key,以及每个分组中的原始字符串。

因此,总的额外空间复杂度为:

\ O(nk) \\

六、最后总结

本题是 哈希表 + 排序 的经典应用,重点掌握以下几点:

  1. 为什么使用 unordered_map? 因为需要建立排序后字符串与原始字符串集合之间的映射关系,并且不需要对 key 排序。

  2. 为什么将字符串排序后作为 key? 因为字母异位词排序后一定相同,可以用排序结果作为分组依据。

  3. 为什么 value 是 vector<string>? 因为同一个 key 可能对应多个原始字符串,需要用 vector 保存一整组。

  4. 为什么 res[key] 可以直接使用? 因为 unordered_map 的 operator[] 在 key 不存在时,会自动创建一个默认初始化的 value。这里就是空的 vector<string>。

  5. 为什么使用 emplace_back(x)? 因为需要将当前原始字符串添加到对应分组的末尾。

  6. 为什么最后还需要 ans? 因为题目要求返回 vector<vector<string>>,而不是哈希表,所以需要提取所有 value。

  7. 为什么结果的分组顺序不固定? 因为 unordered_map 是无序容器,不保证遍历顺序。

七、与两数之和的区别

你刚才做的两数之和与这道题都使用了 unordered_map,但用途有所不同。

对比 两数之和 字母异位词分组
哈希表类型 unordered_map<int,int> unordered_map<string,vector<string>>
key 元素值 排序后的字符串
value 元素下标 原始字符串集合
核心操作 find() 查找 emplace_back() 分组
主要作用 快速寻找目标元素 将相同特征的元素归类

一句话总结:

两数之和利用哈希表快速查找元素,而字母异位词分组利用哈希表的 key 唯一性,将具有相同特征的字符串存入同一个 vector。

这道题最重要的是理解:

复制代码
res[key].emplace_back(x);

相同的 key 对应同一个 vector,不同的 key 对应不同的 vector。

这就是哈希表实现分组的核心思想。

相关推荐
weixin_307779131 小时前
C++代码实现MATLAB中的spectralcluster函数功能
开发语言·c++·算法·matlab
行者全栈架构师1 小时前
【鸿蒙心迹】让应用“悬浮“起来——闪控球(floatingBall)从接入到实战全链路(HarmonyOS 7.x)
前端·算法·架构
云的雨 雨的云2 小时前
手写算法动画上位机:从排序到线性代数可视化
线性代数·算法
Pniubi2 小时前
快速排序(力扣基础)(递归)
java·数据结构·算法
roman_日积跬步-终至千里2 小时前
【数据Agent论文·分析与报告评测|27】ComInsight:原子洞察正确,组合后的结论就可靠吗?
人工智能·算法·机器学习·数据分析
Samson Bruce2 小时前
【数学之美】
线性代数·算法·概率论
Mister Leon3 小时前
Nvidia Orin AGX实战之从量化到蒸馏:在 Orin 上落地的大模型压缩实战
人工智能·算法·机器学习
aqiu1111113 小时前
蓝桥杯真题复盘:Fibonacci 数列与黄金分割(C++ 算法详解)
c++·算法·蓝桥杯
phoenix@Capricornus3 小时前
LDA 协方差矩阵(MATLAB `fitcdiscr`)
算法·matlab·矩阵