KingOfEgg
首页项目归档照片墙音乐灵境说说杂谈友链关于
封面

Leetcode hot100 字母异位词分组

2026-09-28 20:53:11
# LeetCode
# 哈希表
# 字符串

题目:给你一个字符串数组,将字母异位词(字母相同、排列不同的单词,如 eat 与 tea)组合在一起,返回分组结果。分组内顺序任意,输出顺序任意。

思路:互为异位词的单词,其字符重排后相同。据此可以为每个单词构造一个"规范键"(排序后的字符串,或 26 个字母的计数),将键相同的单词归入同一组,用哈希表完成分组。

解法一:排序作为键

Python

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        groups = defaultdict(list)
        for s in strs:
            key = ''.join(sorted(s))
            groups[key].append(s)
        return list(groups.values())

逐行解释:

行 代码 解释
3 groups = defaultdict(list) 哈希表,键为异位词的规范键,值为同组单词列表。defaultdict(list) 在访问不存在的键时自动创建空列表,省去键是否存在的判断
4 for s in strs: 遍历每个单词
5 key = ''.join(sorted(s)) sorted(s) 将字符排序,''.join(...) 拼回字符串。排序结果相同者互为异位词
6 groups[key].append(s) 将单词追加到对应分组;键首次出现时自动建组
7 return list(groups.values()) 取出全部分组,转为列表返回

C++

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> groups;
        for (const string& s : strs) {
            string key = s;
            sort(key.begin(), key.end());
            groups[key].push_back(s);
        }
        vector<vector<string>> res;
        for (auto& [key, group] : groups) {
            res.push_back(group);
        }
        return res;
    }
};

逐行解释:

行 代码 解释
4 unordered_map<string, vector<string>> groups; 哈希表:键为排序后的字符串,值为同组单词。基于哈希实现,单次操作平均 O(1)
5 for (const string& s : strs) 范围 for 遍历,常量引用避免拷贝
6 string key = s; 拷贝一份用于排序,不能排序原单词(其为 const,且原数据仍需保留)
7 sort(key.begin(), key.end()); 原地按字典序排序,如 "eat" → "aet"
8 groups[key].push_back(s); 键不存在时 operator[] 自动默认构造空桶,再执行 push_back
10 vector<vector<string>> res; 结果数组
11 for (auto& [key, group] : groups) C++17 结构化绑定遍历键值对,group 即某一组
12 res.push_back(group); 逐组加入结果。C++ 不像 Python 可直接取 values(),须手动遍历拼装

解法二:字符计数作为键(避免排序)

Python

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        groups = defaultdict(list)
        for s in strs:
            count = [0] * 26
            for c in s:
                count[ord(c) - ord('a')] += 1
            groups[tuple(count)].append(s)
        return list(groups.values())

关键行解释:

行 代码 解释
5 count = [0] * 26 长度 26 的计数数组,对应 a~z 出现次数
7 count[ord(c) - ord('a')] += 1 ord(c) - ord('a') 将字符映射为 0~25 下标,计数加一
8 groups[tuple(count)].append(s) 列表不可哈希,须转元组作为键;同组异位词计数元组相同

C++

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // 自定义哈希函数:将 26 个计数映射为一个 size_t
        auto arrayHash = [fn = hash<int>{}](const array<int, 26>& a) -> size_t {
            return accumulate(a.begin(), a.end(), 0ull,
                [&](size_t acc, int x) { return (acc << 1) ^ fn(x); });
        };

        unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> groups(0, arrayHash);

        for (const string& s : strs) {
            array<int, 26> count{};
            for (char c : s) {
                count[c - 'a']++;
            }
            groups[count].push_back(s);
        }

        vector<vector<string>> res;
        for (auto& [key, group] : groups) {
            res.push_back(group);
        }
        return res;
    }
};

关键行解释:

行 代码 解释
5 auto arrayHash = [fn = hash<int>{}](const array<int, 26>& a) -> size_t C++ 的 array<int, 26> 无默认哈希,须自定义。lambda 以初始化捕获引入标准库的 int 哈希器 fn
6 return accumulate(...) 对 26 个计数逐项混合:(acc << 1) ^ fn(x) 是常见哈希合并手法,使不同计数组合尽量映射到不同值
10 unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> groups(0, arrayHash); 键类型为计数数组,第三模板参数指定哈希器,构造时传入
14 array<int, 26> count{}; {} 保证 26 个元素全部初始化为 0
16 count[c - 'a']++; 字符映射为下标,计数加一
18 groups[count].push_back(s); array 支持逐元素 == 比较,计数相同者归入同组

复杂度对比

解法 时间复杂度 空间复杂度 说明
排序键 O(n·k log k) O(n·k) n 为单词数,k 为单词最大长度;每词排序耗时 k log k
计数键 O(n·k) O(n·k) 省去排序的 log 因子

结论:计数法理论复杂度更优,排序法键为短字符串、实现简洁。词长较短时二者实际差距很小,面试可先写排序法,口头补充计数法作为优化;若面试官明确要求避免排序,再给出计数法实现。

avatar

KingOfEgg

otaku change the world

2026年9月

一
二
三
四
五
六
日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Recent Records

Leetcode hot128 最长连续序列:排序解法杂谈

2026-09-28 21:56:14