问题简介
题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例说明
✅ 示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
✅ 示例 2:
输入: strs = [""]
输出: [[""]]
✅ 示例 3:
输入: strs = ["a"]
输出: [["a"]]
解题思路
💡 方法一:排序作为键(推荐)
核心思想 :
字母异位词在排序后会变成相同的字符串。我们可以将每个字符串排序后的结果作为哈希表的 key,value 则是具有相同 key 的原始字符串列表。
步骤如下:
- 创建一个哈希表
Map<String, List<String>>。 - 遍历字符串数组中的每一个字符串:
- 将当前字符串转为字符数组并排序;
- 将排序后的字符数组转回字符串,作为 key;
- 将原始字符串加入到该 key 对应的列表中。
- 最终将哈希表的所有 value 收集为结果列表。
✅ 优点:实现简单、直观,适用于大多数场景。
💡 方法二:字符计数作为键
核心思想 :
两个字符串是字母异位词,当且仅当它们每个字符出现的次数完全相同。因此,我们可以统计每个字符串中 26 个字母的出现次数,将其编码为一个唯一标识(如 "a2b1c0..."),作为哈希表的 key。
步骤如下:
- 创建哈希表
Map<String, List<String>>。 - 对每个字符串:
- 初始化长度为 26 的计数数组;
- 遍历字符串,统计每个字符出现次数;
- 将计数数组编码为字符串(如
"1#2#0#...#0"); - 以该编码为 key,将原字符串加入对应列表。
- 返回所有 value。
✅ 优点 :避免了排序开销,理论上时间复杂度更优(但常数较大)。
❌ 缺点:编码逻辑稍复杂,且对于非小写字母需扩展处理。
📌 本题假设只包含小写字母,故方法二可行;若包含大小写或 Unicode,则方法一更通用。
代码实现
java:Java
import java.util.*;
class Solution {
// 方法一:排序作为 key
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
char[] chars = s.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(map.values());
}
// 方法二:字符计数作为 key(可选)
public List<List<String>> groupAnagramsCount(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
int[] count = new int[26];
for (char c : s.toCharArray()) {
count[c - 'a']++;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 26; i++) {
sb.append(count[i]).append('#');
}
String key = sb.toString();
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(map.values());
}
}
go:Go
package main
import (
"sort"
"strings"
)
// 方法一:排序作为 key
func groupAnagrams(strs []string) [][]string {
groups := make(map[string][]string)
for _, s := range strs {
// 将字符串转为字节切片并排序
bytes := []byte(s)
sort.Slice(bytes, func(i, j int) bool {
return bytes[i] < bytes[j]
})
key := string(bytes)
groups[key] = append(groups[key], s)
}
// 提取所有分组
result := make([][]string, 0, len(groups))
for _, group := range groups {
result = append(result, group)
}
return result
}
// 方法二:字符计数作为 key(可选)
func groupAnagramsCount(strs []string) [][]string {
groups := make(map[string][]string)
for _, s := range strs {
count := make([]int, 26)
for _, c := range s {
count[c-'a']++
}
var key strings.Builder
for _, c := range count {
key.WriteString(strconv.Itoa(c) + "#")
}
k := key.String()
groups[k] = append(groups[k], s)
}
result := make([][]string, 0, len(groups))
for _, group := range groups {
result = append(result, group)
}
return result
}
⚠️ 注意:Go 中
strconv.Itoa需要导入strconv包(方法二中未显式写出 import,实际使用需添加)。
示例演示
以输入 ["eat", "tea", "tan", "ate", "nat", "bat"] 为例:
| 原字符串 | 排序后 key | 分组情况 |
|---|---|---|
| "eat" | "aet" | ["eat"] |
| "tea" | "aet" | ["eat", "tea"] |
| "tan" | "ant" | ["tan"] |
| "ate" | "aet" | ["eat", "tea", "ate"] |
| "nat" | "ant" | ["tan", "nat"] |
| "bat" | "abt" | ["bat"] |
最终分组:[["eat","tea","ate"], ["tan","nat"], ["bat"]]
答案有效性证明
-
✅ 正确性 :
若两个字符串是字母异位词 ⇨ 它们排序后相等 ⇨ 被分到同一组。
反之,若排序后相等 ⇨ 字符组成完全相同 ⇨ 是字母异位词。
因此,分组结果满足题目要求。
-
✅ 完备性 :
所有字符串都会被处理并加入某个分组,无遗漏。
-
✅ 无重复 :
每个字符串仅被加入一次,且分组之间互斥。
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 排序法 | O ( N c d o t K l o g K ) O(N \\cdot K \\log K) O(NcdotKlogK) | O ( N c d o t K ) O(N \\cdot K) O(NcdotK) | N N N 为字符串数量, K K K 为平均字符串长度。排序每个字符串耗时 O ( K l o g K ) O(K \\log K) O(KlogK)。 |
| 计数法 | O ( N c d o t K ) O(N \\cdot K) O(NcdotK) | O ( N c d o t K + 26 c d o t N ) a p p r o x O ( N c d o t K ) O(N \\cdot K + 26 \\cdot N) \\approx O(N \\cdot K) O(NcdotK+26cdotN)approxO(NcdotK) | 每个字符串遍历一次统计字符,编码固定长度(26),无排序开销。 |
📌 实际中,由于 K K K 通常较小(如 ≤ 100),排序法常数小、代码简洁,更常用。
问题总结
- ✅ 关键洞察:字母异位词具有相同的"规范形式"------可通过排序或字符频次唯一确定。
- ✅ 哈希表是核心工具:用于将具有相同特征的元素聚合。
- ✅ 两种方法权衡 :
- 排序法:简洁、通用、易写,适合面试和工程。
- 计数法:理论更优 ,但编码复杂,仅在 K K K 很大时有优势。
- 💡 扩展思考 :若字符串包含 Unicode 字符(如中文),排序法依然适用,而计数法需改用
Map<Character, Integer>并序列化为 key。
🎯 推荐掌握排序法,它是解决此类"归一化分组"问题的经典范式。
github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions