【Hot100 | 2 LeetCode49 字母异位词分组问题】


要理解这行代码,我们需要先结合它的应用场景 ------LeetCode 字母异位词分组问题(将由相同字母组成、仅顺序不同的字符串分到同一组,如 "eat" 和 "tea" 为一组),再拆解代码的核心逻辑、关键 API 特性和背后的设计思路:

一、先明确场景背景

字母异位词的核心特征是:排序后会得到完全相同的字符串。例如:

  • "eat" → 排序后 → "aet"
  • "tea" → 排序后 → "aet"
  • "tan" → 排序后 → "ant"

因此,我们可以用「排序后的字符串」作为分组的 key,用「存储原字符串的列表」作为分组的 value,通过 HashMap 实现快速分组。而你贴的代码,正是这一逻辑的「简洁实现核心」。

二、逐部分拆解代码

代码:m.computeIfAbsent(new String(sortedS), _ -> new ArrayList<>()).add(s);其中 mHashMap<String, List<String>> 类型(key:排序后的字符串,value:同组原字符串列表),sortedS 是原字符串 s 排序后的 char[] 数组,s 是原字符串。

我们按「从左到右」的顺序拆解每个部分:

1. 核心 API:HashMap.computeIfAbsent(key, mappingFunction)

这是 HashMap 的高频实用方法,作用是:

  • 先检查 HashMap 中是否存在指定的 key
  • 不存在 :则执行 mappingFunction(映射函数)生成一个「默认值」,将 key→默认值 存入 HashMap,最后返回这个「默认值」;
  • 已存在 :直接返回 key 对应的「已有值」。

简单说:它帮我们简化了「判断 key 是否存在 → 不存在则创建值 → 返回值」的三步操作,避免了手动写 if (m.get(key) == null) { m.put(key, new ArrayList<>()); } 这类冗余代码。

2. 第一个参数:new String(sortedS)(分组的 key)
  • sortedS 是原字符串 s 排序后的 char 数组 (例如 s = "eat"sortedS = {'a','e','t'});

  • 为什么要 new String(sortedS)?因为 char 数组不能直接作为 HashMap 的 key !HashMap 判断 key 是否相等,依赖 key.equals() 方法:

    • char 数组的 equals() 是「引用比较」(即比较两个数组是否是同一个对象),哪怕内容相同,不同数组也会被认为是不同 key;
    • String 的 equals() 是「内容比较」(比较字符串的字符序列),只要排序后字符相同,new String(sortedS) 得到的字符串就会被认为是同一个 key。

    例:"eat""tea" 排序后的 char 数组内容相同,new String(sortedS) 都会得到 "aet",因此会被分到同一个 key 下。

3. 第二个参数:_ -> new ArrayList<>()(映射函数)

这是一个 Lambda 表达式 ,作为 computeIfAbsent 的「默认值生成器」:

  • Lambda 中的 _ 是「占位符」,表示「不关心这个参数」(因为 computeIfAbsent 的映射函数会传入 key 作为参数,但我们创建空列表时不需要用到 key,所以用 _ 省略);
  • key 不存在时,这个 Lambda 会生成一个 空的 ArrayList,作为该 key 对应的初始 value(用来存储同组的原字符串)。
4. 最后一步:.add(s)(添加原字符串到分组)

computeIfAbsent 会返回「key 对应的 value 列表」(无论是新创建的空列表,还是已有的列表),然后调用 add(s),将原字符串 s 加入到这个列表中 ------ 这就完成了「将当前字符串分到对应组」的最终操作。

三、用具体例子演示完整流程

假设我们要处理三个字符串:s1 = "eat"s2 = "tea"s3 = "tan",初始 m 是空的 HashMap。

步骤 1:处理 s1 = "eat"
  1. sortedS1 = {'a','e','t'}key1 = new String(sortedS1) = "aet"
  2. 检查 m 中是否有 "aet"?无;
  3. 执行 Lambda 生成空列表 new ArrayList<>(),将 "aet" → [] 存入 m
  4. 返回这个空列表,调用 add("eat") → 列表变成 ["eat"]
  5. 此时 m = {"aet": ["eat"]}
步骤 2:处理 s2 = "tea"
  1. sortedS2 = {'a','e','t'}key2 = "aet"
  2. 检查 m 中是否有 "aet"?有,对应列表 ["eat"]
  3. 直接返回该列表,调用 add("tea") → 列表变成 ["eat", "tea"]
  4. 此时 m = {"aet": ["eat", "tea"]}
步骤 3:处理 s3 = "tan"
  1. sortedS3 = {'a','n','t'}key3 = "ant"
  2. 检查 m 中是否有 "ant"?无;
  3. 生成空列表,存入 m"ant": []
  4. 调用 add("tan") → 列表变成 ["tan"]
  5. 最终 m = {"aet": ["eat", "tea"], "ant": ["tan"]},完成分组。

四、核心优势与注意点

1. 优势
  • 代码简洁:一行替代了「判断 key 存在性 + 创建列表 + 存入 map + 添加元素」的多步操作;
  • 效率高效computeIfAbsent 是 HashMap 的原生方法,平均时间复杂度 O (1),配合排序(O (n log n),n 是字符串长度),整体算法时间复杂度为 O (m * n log n)(m 是字符串总数),是该问题的最优解法之一。
2. 注意点
  • char 数组转 String 不可省略 :必须用 new String(sortedS) 作为 key,不能直接用 sortedS(否则分组失败);
  • Lambda 中的占位符_ 是 Java 8+ 支持的语法,表示「未使用的参数」,如果用 IDE 编译报错,可替换为 key -> new ArrayList<>()(虽然 key 没用到,但语法正确)。

通过这行代码,我们能清晰看到「利用 HashMap 分组 + 排序生成统一 key」的设计思路,也是大厂面试中常考的「哈希表应用 + 字符串处理」经典场景。

代码逐行解析

java

运行

复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        // 1. 创建哈希表:key是排序后的字符串,value是同组原字符串的列表
        Map<String, List<String>> m = new HashMap<>();
        
        // 2. 遍历输入的每个字符串
        for (String s : strs) {
            // 2.1 将字符串转换为字符数组(方便排序)
            char[] sortedS = s.toCharArray();
            
            // 2.2 对字符数组排序(异位词排序后会得到相同的字符序列)
            Arrays.sort(sortedS);
            
            // 2.3 用排序后的字符数组生成key(必须转为String,因为char[]不能作为HashMap的key)
            // 2.4 用computeIfAbsent处理哈希表:若key不存在则创建空列表,最后将原字符串s加入列表
            m.computeIfAbsent(new String(sortedS), _ -> new ArrayList<>()).add(s);
        }
        
        // 3. 哈希表的value集合就是所有分组,转换为List返回
        return new ArrayList<>(m.values());
    }
}
关键步骤拆解:
  1. 哈希表初始化 Map<String, List<String>> m = new HashMap<>():定义一个哈希表,键(key)是 "排序后的字符串"(作为异位词的统一标识),值(value)是List<String>(存储所有属于该组的原字符串)。

  2. 遍历字符串并处理 对每个原字符串s

    • char[] sortedS = s.toCharArray():将字符串转为字符数组(因为字符串是不可变的,而数组可以排序)。
    • Arrays.sort(sortedS):对字符数组排序(例如 "tea" → 排序后字符数组为 ['a','e','t'])。
    • new String(sortedS):将排序后的字符数组转回字符串(作为哈希表的 key)。这里必须转成 String,因为char [] 不能作为 HashMap 的 key (数组的equals是引用比较,而 String 的equals是内容比较,确保异位词的 key 相同)。
  3. 核心 API:computeIfAbsent m.computeIfAbsent(key, mappingFunction).add(s)

    • 作用:检查哈希表中是否存在key。若不存在,执行mappingFunction(这里是创建空列表)并存入哈希表;最后返回key对应的列表。
    • 效果:一行代码完成 "判断 key 是否存在→不存在则创建列表→将原字符串加入列表" 的操作,简化逻辑。
  4. 返回结果 return new ArrayList<>(m.values()):哈希表的values()方法返回所有分组的列表(Collection<List<String>>),将其转换为List<List<String>>即可作为结果返回。

四、复杂度分析

  • 时间复杂度O(n * k log k)
    • n是输入字符串的数量,k是字符串的最大长度。
    • 遍历所有字符串耗时O(n);对每个字符串排序耗时O(k log k)(排序算法的时间复杂度)。
  • 空间复杂度O(n * k)
    • 哈希表需要存储所有字符串,总空间取决于所有字符串的总长度(n * k)。

总结

这段代码的核心是利用 "异位词排序后相同" 的特性,通过哈希表快速分组,逻辑清晰且效率较高。其中computeIfAbsent方法的使用简化了代码,是哈希表操作的高频技巧,也是面试中的常见考点。


优化方向?

相关推荐
2301_795167202 小时前
Rust 在内存安全方面的设计方案的核心思想是“共享不可变,可变不共享”
算法·安全·rust
czhc11400756633 小时前
Java117 最长公共前缀
java·数据结构·算法
java 乐山3 小时前
蓝牙网关(备份)
linux·网络·算法
云泽8083 小时前
快速排序算法详解:hoare、挖坑法、lomuto前后指针与非递归实现
算法·排序算法
数字化脑洞实验室3 小时前
智能决策算法的核心原理是什么?
人工智能·算法·机器学习
流烟默3 小时前
机器学习中拟合、欠拟合、过拟合是什么
人工智能·算法·机器学习
Brianna Home3 小时前
现代C++:从性能泥潭到AI基石
开发语言·c++·算法
再卷也是菜3 小时前
算法基础篇(10)递归型枚举与回溯剪枝
算法·深度优先·剪枝
吃着火锅x唱着歌3 小时前
LeetCode 2016.增量元素之间的最大差值
数据结构·算法·leetcode