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

相关推荐
lixinnnn.2 小时前
字符串拼接:Cities and States S
开发语言·c++·算法
AI街潜水的八角2 小时前
医学图像算法之基于MK_UNet的肾小球分割系统3:含训练测试代码、数据集和GUI交互界面
算法
larance2 小时前
方差和标准差
人工智能·算法·机器学习
TracyCoder1232 小时前
LeetCode Hot100(3/100)——128.最长连续序列
算法·leetcode
Piar1231sdafa2 小时前
【目标检测】竹林与杂草识别_YOLOv26改进算法研究
算法·yolo·目标检测
风筝在晴天搁浅3 小时前
hot100 543.二叉树的直径
算法·深度优先
风筝在晴天搁浅3 小时前
hot100 102.二叉树的层序遍历
java·算法
张祥6422889043 小时前
误差理论与测量平差基础笔记八
笔记·算法·机器学习
进击的小头3 小时前
传递函数与系统特性(核心数学工具)
python·算法·数学建模