【LeetCode Hot100刷题日记(2/100)】字母异位词分组——哈希、字符串、排序

143 阅读5分钟

🔥【算法2/100】LeetCode Hot 100 刷题日记:第2题「字母异位词分组」详解 💡

专栏:【算法】LeetCode Hot 100 刷题日记
题目编号:#49
题目名称:字母异位词分组(Group Anagrams)
难度:🟡 中等
标签:哈希表、字符串、排序


📌 题目链接

leetcode.cn/problems/gr…

image.png

image.png

🧠 题目分析

字母异位词是指由相同字母重新排列形成的不同单词,如"eat"、"tea"和"ate"就是一组字母异位词。本题需要将给定的字符串数组中的字母异位词分组归类。关键在于找到字母异位词的共同特征作为分组的依据。

🔍 小知识补充
“字母异位词”是英文 “anagram” 的翻译,常出现在密码学、文字游戏和自然语言处理中 🎯。例如:“listen” 和 “silent” 是经典例子。这道题考察的是如何抽象出不变特征来分类数据——这是哈希思想的核心应用之一!


⚙️ 核心算法及代码讲解

本解法采用排序法作为核心算法。其原理是:字母异位词排序后会得到相同的字符串,可以将这个排序后的字符串作为哈希表的键,原始字符串作为值进行分组。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> data;  // 哈希表:键为排序后的字符串,值为原字符串列表

        for (const auto& s : strs) {          // 遍历每个字符串
            auto key = s;                     // 复制原字符串用于排序
            sort(key.begin(), key.end());     // 将字符串排序得到键(字母异位词排序后相同)
            data[key].push_back(s);           // 将原字符串加入对应键的分组中
        }

        vector<vector<string>> ret;           // 定义结果向量
        for (const auto& p : data) {         // 遍历哈希表中的所有分组
            ret.push_back(p.second);          // 将每个分组加入结果向量
        }
        return ret;                          // 返回最终分组结果
    }
};

💡 技巧提示

  • 使用 auto key = s 复制字符串是为了避免修改原字符串。
  • sort(key.begin(), key.end()) 实现了字符级别的排序,时间复杂度 O(k log k),k 为字符串长度。
  • data[key].push_back(s) 自动创建新桶或追加元素,C++ 容器自动扩容。

🧩 解题思路

  1. 理解问题本质:字母异位词具有相同的字母组成,只是排列顺序不同
  2. 确定分组特征:将每个字符串排序,字母异位词排序后会得到相同的字符串
  3. 建立映射关系:使用哈希表,以排序后的字符串为键,原始字符串列表为值
  4. 遍历处理:对每个字符串进行排序操作,然后将其添加到哈希表对应的分组中
  5. 输出结果:将哈希表中所有的值收集起来即为最终分组结果

🧠 为什么排序能作为“指纹”?
因为字母异位词的字符集合完全一致,只是顺序不同。排序后它们变成唯一标识符,就像每个人的身份证号一样 👤。这种“规范化”策略是解决分类问题的常用手段。


📊 算法分析

时间复杂度:O(n × k × log k),其中n是字符串数量,k是字符串最大长度。每个字符串排序需要O(k × log k)时间,共n个字符串
空间复杂度:O(n × k),哈希表需要存储所有字符串
优点:实现简单直观,代码易于理解和维护
缺点:当字符串较长时排序操作开销较大

📊 对比其他解法

方法时间复杂度空间复杂度是否可行
排序法(本解法)O(n×k×log k)O(n×k)✅ 简洁高效
计数法(优化版)O(n×k)O(k)✅ 更优
暴力比较O(n²×k)O(1)❌ 不推荐

计数法简介:用长度为26的数组记录每个字符出现次数,转为元组作为 key,避免排序。适用于只含小写字母的情况。


💻 代码

// Leetcode49.字母异位词分组
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> data;  // 哈希表:排序后的字符串 -> 原字符串列表

        for (const auto& s : strs) {          // 遍历每个字符串
            auto key = s;                     // 复制原字符串用于排序
            sort(key.begin(), key.end());     // 排序字符串作为键
            data[key].push_back(s);           // 将原字符串加入对应分组
        }

        vector<vector<string>> ret;           // 定义结果向量
        for (const auto& p : data) {         // 遍历哈希表
            ret.push_back(p.second);          // 将分组加入结果
        }
        return ret;                          // 返回最终结果
    }
};

int main() {
    Solution solution;

    // 测试用例1
    vector<string> strs1 = { "eat", "tea", "tan", "ate", "nat", "bat" };
    auto result1 = solution.groupAnagrams(strs1);

    cout << "测试用例1结果:" << endl;
    for (const auto& group : result1) {
        for (const auto& str : group) {
            cout << str << " ";
        }
        cout << endl;
    }

    // 测试用例2
    vector<string> strs2 = { "" };
    auto result2 = solution.groupAnagrams(strs2);

    cout << "\n测试用例2结果:" << endl;
    for (const auto& group : result2) {
        for (const auto& str : group) {
            cout << "\"" << str << "\" ";
        }
        cout << endl;
    }

    // 测试用例3
    vector<string> strs3 = { "a" };
    auto result3 = solution.groupAnagrams(strs3);

    cout << "\n测试用例3结果:" << endl;
    for (const auto& group : result3) {
        for (const auto& str : group) {
            cout << str << " ";
        }
        cout << endl;
    }

    return 0;
}

🧪 调试建议

  • 注意空字符串 "" 的处理,排序后仍为空。
  • 单字符字符串 "a" 也会被正确分组。
  • 可以打印中间变量(如 key)来验证逻辑。

🌱 学习延伸(新增内容)

✅ 举一反三

  • 变体1:按字典序输出每组 → 在 ret 中排序即可。
  • 变体2:返回分组的索引而非字符串 → 可用 vector<int> 存储原下标。
  • 变体3:支持大小写混合 → 先统一转为小写再处理。

📚 推荐掌握的数据结构

  • C++unordered_map + vector
  • Pythoncollections.defaultdict(list)
  • JavaHashMap<String, List<String>>

💬 面试高频问法

“有没有更高效的解法?”
👉 回答:可以用字符计数法,比如用 array[26] 统计每个字母频次,然后转成字符串或元组作为 key,避免排序,时间复杂度降至 O(n×k)。


结语
《字母异位词分组》是一道典型的“分类+哈希”题,展现了如何通过特征提取将复杂问题简化。它不仅是面试常客,更是后续字符串处理题的基础 🧱。掌握它,你就迈出了“哈希思维”的第二步!

📌 下一期预告:LeetCode 热题 100 第3题 —— 最长连续序列(中等)
🎯 题目:给定一个未排序的整型数组,找出数字连续的最长序列的长度。
🔧 核心思路:使用哈希表存储所有数字,遍历每个数字时向左右扩展计算连续序列长度,并标记已访问的数字避免重复计算。
💡 这是哈希表与动态规划结合的经典案例,要求时间复杂度 O(n),挑战更高!