LeetCode Hot100(2/100)——49. 字母异位词分组 (Group Anagrams)。

文章目录

题目描述

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是指由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

示例 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 对短字符串非常快。如果面试官问"如果字符串特别长怎么办?",再引出 解法二(计数法) 作为优化方案,这样可以展示你对算法复杂度的深刻理解。

相关推荐
小O的算法实验室16 小时前
2026年ASOC,基于深度强化学习的无人机三维复杂环境分层自适应导航规划方法,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
郭涤生17 小时前
STL vector 扩容机制与自定义内存分配器设计分析
c++·算法
༾冬瓜大侠༿17 小时前
vector
c语言·开发语言·数据结构·c++·算法
Ricky111zzz17 小时前
leetcode学python记录1
python·算法·leetcode·职场和发展
汀、人工智能17 小时前
[特殊字符] 第58课:两个正序数组的中位数
数据结构·算法·数据库架构··数据流·两个正序数组的中位数
liu****17 小时前
第16届省赛蓝桥杯大赛C/C++大学B组(京津冀)
开发语言·数据结构·c++·算法·蓝桥杯
汀、人工智能17 小时前
[特殊字符] 第79课:分割等和子集
数据结构·算法·数据库架构·位运算·哈希表·分割等和子集
汀、人工智能17 小时前
[特殊字符] 第74课:完全平方数
数据结构·算法·数据库架构·图论·bfs·完全平方数
CoderCodingNo18 小时前
【GESP】C++四、五级练习题 luogu-P1177 【模板】排序
数据结构·c++·算法
Proxy_ZZ018 小时前
从零实现LDPC比特翻转译码器:C语言实战与底层逻辑解析
c语言·算法