LeetCode 面试题 10.02. 变位词组
题目描述

题意分析
将字符串按变位词关系分组:同组字符串的字母种类和每种字母的出现次数相同,只是排列顺序可以不同。输入只包含小写字母,答案不要求组间或组内顺序,但每个输入字符串都要保留在对应分组中。
解法:排序签名 + 哈希分组
核心思路
[!blue]
逐个把字符串与已有字符串比较,会重复检查许多字符。可以先把每个字符串转换成一个代表整组的规范键,再用哈希表直接定位分组。
将字符排序后,同一种字母的所有副本会排在一起,原来的排列差异被消除。若两个字符串互为变位词,它们的各字母频次相同,排序结果一定相同;反过来,排序结果相同也说明每个字母及其次数完全一致。因此“排序后的字符串相同”恰好等价于“属于同一组”。
哈希表
d保存“排序签名 → 原字符串列表”。遍历时把当前字符串复制为字符数组t并排序,得到键k,然后把原字符串s追加到d[k]。排序只用于构造键,输出仍保留输入的原始内容。每个输入只追加一次,所以不会遗漏或额外产生字符串;相同键必属同组,不同键必属不同组,分组也不会混淆。重复出现的相同字符串会按出现次数保留,空字符串则以空键进入同一组。题目不要求输出顺序,最后直接收集哈希表中的所有列表即可。
解题步骤
- 创建从签名字符串到分组列表的哈希表。
- 对每个输入字符串,复制其字符并排序,将排序结果转换成键。
- 获取或创建该键的分组,把原字符串追加进去。
- 全部输入处理完后,将哈希表里的分组列表作为结果返回。
代码实现
// 排序后的字符序列是变位词组的规范签名。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> d = new HashMap<>();
for (String s : strs) {
char[] t = s.toCharArray();
Arrays.sort(t);
String k = String.valueOf(t);
d.computeIfAbsent(k, key -> new ArrayList<>()).add(s);
}
return new ArrayList<>(d.values());
}
}
import (
"sort"
)
// 排序后的字符序列是变位词组的规范签名。
func groupAnagrams(strs []string) (answer [][]string) {
d := map[string][]string{}
for _, s := range strs {
t := []byte(s)
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
k := string(t)
d[k] = append(d[k], s)
}
for _, v := range d {
answer = append(answer, v)
}
return
}
复杂度分析
- 时间复杂度:设有 $n$ 个字符串,第 $i$ 个长度为 $k_i$。按哈希操作的期望开销计算,总时间为 $O(n+\sum_i k_i\log(k_i+1))$,包括逐串遍历、复制字符、排序和计算键的哈希;即使字符串为空,也需要一次分组操作。
- 空间复杂度:$O(n+\sum_i k_i)$,用于保存各组的签名和原字符串引用,当前用于排序的字符副本也包含在这个上界内。
关键点总结
[!green]
- 规范键相等当且仅当互为变位词,使逐个分组可以直接转为哈希查找。
- 键保存排序后的字符,组内保存原字符串,两者用途不同。
- 频次必须保留,重复字符串也按输入次数追加,不能用集合代替分组列表。
易错点总结
[!yellow]
- 只用字符集合做键会丢失字母次数,把不属于同组的字符串混在一起。
- 把排序签名而不是原字符串加入结果,会改变用户给定的字符串内容。
- 找到已有分组后应追加元素,不能用当前字符串覆盖整个列表。
- Java
HashMap和 Gomap不保证固定遍历顺序;本题允许任意输出顺序,不能据此要求答案与示例顺序完全一致。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 438. 找到字符串中所有字母异位词 | 中等 | 都需要识别字母频次相同,本题把完整字符串分组,原题在滑动窗口中寻找匹配。 |
| 面试题 01.02. 判定是否互为字符重排 | 简单 | 从判断两个串是否同类,扩展为给多个串计算规范键并聚合。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!