文章目录
-
-
- 题目描述
- 核心解题思路:哈希表分组
- 解法一:排序数组分类法 (Categorize by Sorted String)
-
- [1. 思路解析](#1. 思路解析)
- [2. 逻辑图解 (Mermaid)](#2. 逻辑图解 (Mermaid))
- [3. Java 代码实现](#3. Java 代码实现)
- [4. 复杂度分析](#4. 复杂度分析)
- 解法二:计数法 (Categorize by Count)
-
- [1. 思路解析](#1. 思路解析)
- [2. 状态映射流程 (Mermaid)](#2. 状态映射流程 (Mermaid))
- [3. Java 代码实现](#3. Java 代码实现)
- [4. 复杂度分析](#4. 复杂度分析)
- 总结对比
-
题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是指由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例 1:
输入:
strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:[["bat"],["nat","tan"],["ate","eat","tea"]]
核心解题思路:哈希表分组
无论采用何种具体方法,本题的根本思想都是利用 哈希表 (HashMap) 进行分组。
我们需要寻找一种映射规则,使得所有互为"字母异位词"的字符串,都能生成同一个 Key。
Map: Key (特征值) → Value (字符串列表) \text{Map: } \quad \text{Key (特征值)} \rightarrow \text{Value (字符串列表)} Map: Key (特征值)→Value (字符串列表)
常见的两种生成 Key 的方法是:排序法 和 计数法。
解法一:排序数组分类法 (Categorize by Sorted String)
1. 思路解析
如果是字母异位词,说明它们包含的字母和数量完全相同,只是顺序不同。
如果我们把字符串中的字符按字母顺序排序,那么异位词排序后的结果一定是相同的。
例如:
"eat"→ \rightarrow → 排序 → \rightarrow →"aet""tea"→ \rightarrow → 排序 → \rightarrow →"aet""ate"→ \rightarrow → 排序 → \rightarrow →"aet"
我们将排序后的字符串 "aet" 作为哈希表的 Key,原字符串作为 Value 加入对应的列表。
2. 逻辑图解 (Mermaid)
HashMap
Processing
Input_Array
Key
Key
Key
eat
tea
tan
ate
nat
bat
aet
ant
abt
List: [eat, tea, ate]
List: [tan, nat]
List: [bat]
3. Java 代码实现
java
import java.util.*;
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 创建哈希表,Key是排序后的字符串,Value是原始字符串列表
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
// 1. 将字符串转换为字符数组并排序
char[] charArray = str.toCharArray();
Arrays.sort(charArray);
// 2. 将排序后的字符数组转回字符串,作为Key
String key = new String(charArray);
// 3. 如果Key不存在,初始化一个空列表
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
// 4. 将原始字符串加入对应的列表
map.get(key).add(str);
}
// 返回哈希表中所有的 Values
return new ArrayList<>(map.values());
}
}
4. 复杂度分析
假设数组中有 N N N 个字符串,每个字符串的最大长度为 K K K。
- 时间复杂度 : O ( N ⋅ K log K ) O(N \cdot K \log K) O(N⋅KlogK)。
- 遍历 N N N 个字符串。
- 对每个字符串排序需要 O ( K log K ) O(K \log K) O(KlogK) 的时间。
- 空间复杂度 : O ( N ⋅ K ) O(N \cdot K) O(N⋅K)。
- 哈希表需要存储所有的字符串。
解法二:计数法 (Categorize by Count)
1. 思路解析
如果字符串很长,排序的时间成本较高。我们可以利用字符集通常较小(仅包含小写字母 a-z)这一特点。
我们可以统计每个字符串中各个字符出现的次数。
"eat"→ \rightarrow →a:1, b:0, ..., e:1, ..., t:1"tea"→ \rightarrow →a:1, b:0, ..., e:1, ..., t:1
我们可以把这个计数数组(长度为26)转换成一个唯一的字符串或对象,作为哈希表的 Key。
例如,将计数数组转为字符串 key:"1#0#0#0#1...#1..." (每个数字代表 a-z 的出现次数,中间加分隔符防止歧义)。
2. 状态映射流程 (Mermaid)
哈希表 Key生成器 计数器(int[26]) 原始字符串 哈希表 Key生成器 计数器(int[26]) 原始字符串 "duo", "odu" 扫描 "duo" d=1, o=1, u=1 (其他为0) 生成 Key "0 Put "duo" into List 扫描 "odu" d=1, o=1, u=1 生成 Key (相同) Put "odu" into List (Same Bucket)
3. Java 代码实现
java
import java.util.*;
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
// 1. 创建计数数组,统计每个字符出现的次数
int[] counts = new int[26];
for (int i = 0; i < str.length(); i++) {
counts[str.charAt(i) - 'a']++;
}
// 2. 将计数数组转换为唯一的 Key
// 例如:eat -> "1_0_0_0_1_..._1" (这里使用 StringBuilder 构建)
StringBuilder sb = new StringBuilder();
for (int count : counts) {
sb.append(count).append('#'); // 加个分隔符
}
String key = sb.toString();
// 3. 存入哈希表
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
map.get(key).add(str);
}
return new ArrayList<>(map.values());
}
}
4. 复杂度分析
假设数组中有 N N N 个字符串,每个字符串的最大长度为 K K K。
- 时间复杂度 : O ( N ⋅ K ) O(N \cdot K) O(N⋅K)。
- 我们需要遍历 N N N 个字符串。
- 对于每个字符串,我们需要线性扫描一遍统计字符,耗时 O ( K ) O(K) O(K)。
- 生成 Key 的过程是常数级别的(因为字母表只有26个),严格来说是 O ( N ⋅ ( K + ∣ Σ ∣ ) ) O(N \cdot (K + |\Sigma|)) O(N⋅(K+∣Σ∣)),其中 ∣ Σ ∣ = 26 |\Sigma|=26 ∣Σ∣=26。
- 空间复杂度 : O ( N ⋅ K ) O(N \cdot K) O(N⋅K)。
- 同样需要存储所有字符串。
总结对比
| 维度 | 解法一:排序法 | 解法二:计数法 |
|---|---|---|
| 核心思想 | 异位词排序后相同 | 异位词字符计数相同 |
| Key 的形式 | String (如 "aet") |
String (如 "1#0#0#1...") |
| 时间复杂度 | O ( N ⋅ K log K ) O(N \cdot K \log K) O(N⋅KlogK) | O ( N ⋅ K ) O(N \cdot K) O(N⋅K) |
| 空间复杂度 | O ( N ⋅ K ) O(N \cdot K) O(N⋅K) | O ( N ⋅ K ) O(N \cdot K) O(N⋅K) |
| 适用场景 | K 较小 (单词短),代码极其简洁,面试最常用 | K 较大 (单词非常长),需要极致性能优化时 |
面试建议 :
首选 解法一(排序法) 。因为代码逻辑非常清晰,且 Java 的 Arrays.sort 对短字符串非常快。如果面试官问"如果字符串特别长怎么办?",再引出 解法二(计数法) 作为优化方案,这样可以展示你对算法复杂度的深刻理解。