【LeetCode 热题 100】字母异位分组


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中~



【LeetCode 热题 100】字母异位分组


📝题目描述

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

示例 1:

bash 复制代码
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

解释:

  • 在 strs 中没有字符串可以通过重新排列来形成 "bat";
  • 字符串 "nat" 和 "tan" 是字母异位词,因为它们可以重新排列以形成彼此;
  • 字符串 "ate" ,"eat" 和 "tea" 是字母异位词,因为它们可以重新排列以形成彼此。

示例 2:

bash 复制代码
输入: strs = [""]
输出: [[""]]

示例 3:

bash 复制代码
输入: strs = ["a"]
输出: [["a"]]

💡提示信息

  • 1 <= strs.length <= 10 4 10^4 104;
  • 0 <= strsi.length <= 100;
  • strsi 仅包含小写字母;

核心思路是:要将它们分组,我们需要找到一个共同的特征(Key)。只要两个字符串的"特征"一致,它们就属于同一组。


方法一:排序法

这是最容易想到的解法。既然字母异位词只是顺序不同,那如果我们把它们都按字母顺序重新排序,它们就变成同一个字符串了。

核心逻辑:

  1. 遍历字符串数组,将每个字符串转换为字符数组并进行排序;
  2. 排序后的字符串就是它的 "指纹标识" ;
  3. 使用哈希表存储:Key = 排序后的字符串, Value = 原字符串列表。

Java代码实现如下:

java 复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {

        // 创建一个哈希表接收结果,Key是排序后的字符串,Value是原字符串的列表
        HashMap<String, ArrayList> resultMap = new HashMap<>();

        for(String str : strs){

            // 1. 将字符串转为字符数组
            char[] temp = str.toCharArray();
            // 2. 排序(该方法直接修改原数组,无需接收返回值。)
            Arrays.sort(temp);
             // 3. 转回字符串,作为 Key
            String key = new String(temp);
             // 4. 如果 Key 不存在,初始化一个新的 List
            if(!resultMap.containsKey(key)){
                 // 5. 将原字符串加入对应的组
                resultMap.put(key,new ArrayList());
            }
            resultMap.get(key).add(str);
        }
        
        // 返回所有的分组
        // map.get(key)返回当前key对应的字符串列表List<String>;
        // .add(str) 将原字符串 str 直接追加到该列表末尾
        return new ArrayList(resultMap.values());
    }
}

提交代码,运行结果如下:

  • 执行用时分析:击败 98.13% 的提交者。你的代码运行速度极快,属于第一梯队的解法;
  • 消耗内存分析:击败 25.91% 的提交者。内存占用偏高,处于后 25% 的水平。

方法二:计数哈希法

字母异位词的本质在于字符种类和出现次数相同 。因此,我们可以直接统计字符频率来构建特征,从而将时间复杂度优化至线性级别 O(K)。

核心逻辑

  1. 遍历字符串数组;
  2. 统计每个字符串中 'a' 到 'z' 出现的频率(如使用长度为 26 的数组);
  3. 构建 Key:将这个频率数组转换成一个唯一的字符串作为 Key,存入哈希表(如用 # 分隔每个字符的计数);
  4. 哈希存储:将原字符串存入哈希表对应的分组中。

Java代码实现如下:

java 复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {

        HashMap<String, ArrayList<String>> resultMap = new HashMap<>();

        for(String str:strs){

            // 1. 统计字符频率
            int[] count = new int[26];
            for(char c:str.toCharArray()){
                count[c-'a']++;
            }

            // 2. 将频率数组转化为唯一的 Key。例如 "12" -> "#1#2..." 这种格式
            StringBuilder sb = new StringBuilder();
            for(int freq:count){
                sb.append("#");  // 分隔符
                sb.append(freq);
            }

            // 3. 存入哈希表
            String key = sb.toString();
            if(!resultMap.containsKey(key)){
                resultMap.put(key, new ArrayList<>());
            }
            resultMap.get(key).add(str);
        }
        return new ArrayList(resultMap.values());
    }
}

提交代码:运行结果如下:

观察结果可以发现,和排序法相比,虽然哈希法的时间复杂度降低了,但在力扣上的效果确远不如排序法。原因是:

虽然计数法数学上更优,但在 LeetCode 里却输给了排序法。原因在于测试用例的单词都很短,排序几乎不花时间;而计数法为了生成"指纹",必须死板地遍历 26 个字母并拼接字符串,这些额外的"准备工作"反而比直接排序还要慢。这提醒我们:在数据量级较小时,过度优化反而是一种负担。


对比总结

在解决"字母异位词分组"问题时,排序法计数哈希法分别代表了"代码简洁优先"和"理论性能优先"两种不同的工程思路。虽然在 LeetCode 的特定测试用例下,排序法的实际运行速度往往更快,但这并不代表计数法没有价值。以下是两种方法的详细对比:

维度 排序法 计数哈希法
核心思想 将字符串排序,异位词排序后必相同 统计字符频率,频率分布相同即为异位词
时间复杂度 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)
代码复杂度 极低 (依赖排序) 中等 (需处理数组转字符串逻辑)
通用性 (支持任意字符集,如中文、Emoji) (通常仅限小写字母,扩展性差)
LeetCode 实测 较快 (得益于短字符串 + 底层优化) 较慢 (常数项过大,Key 构建耗时)
  • 在 LeetCode 刷题或日常业务中
    首选 排序法 。因为日常业务中的单词、短语长度( K K K)通常很短, log ⁡ K \log K logK 的增长可以忽略不计,而 Arrays.sort 的底层优化使得其常数极小。排序法代码量少,不易出错,且天然支持各种字符集。
  • 在面试/高阶优化中
    计数哈希法是绝对的加分项。当面试官追问"如果字符串非常长(例如长文本),排序太慢怎么办?"时,提出计数法能体现我们对算法复杂度的深刻理解。

一句话总结

排序法是"短平快"的实战首选,而计数法是应对"超长文本"场景的杀手锏。

这道题不仅考察我们对字符串的处理能力,更是检验我们对 哈希表 这一核心数据结构理解的试金石。

相关推荐
yaoxin52112311 小时前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
noipp11 小时前
推荐题目:洛谷 P10907 [蓝桥杯 2024 国 B] 蚂蚁开会
c语言·c++·算法·编程·洛谷
何极光12 小时前
IDEA集成Maven
java·maven·intellij-idea
程序员二叉12 小时前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉12 小时前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
老马识途2.012 小时前
在AI的帮助下理解spring的启动过程
java·前端·spring
青山木12 小时前
Hot 100 --- 轮转数组
java·数据结构·算法
徐小夕13 小时前
Loop Engineering 深度解析与实战指南(全网最全)
前端·算法·github
Qt程序员13 小时前
掌握 Linux 内核调度:从原理到实现(进程篇)
java·开发语言
code bean13 小时前
【LangChain】检索器完全指南:从向量检索到生产级 RAG 架构
java·开发语言·微服务