【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方法的使用简化了代码,是哈希表操作的高频技巧,也是面试中的常见考点。


优化方向?

相关推荐
业精于勤的牙22 分钟前
三角形最小路径和(二)
算法
风筝在晴天搁浅23 分钟前
hot100 239.滑动窗口最大值
数据结构·算法·leetcode
夏乌_Wx35 分钟前
练题100天——DAY31:相对名次+数组拆分+重塑矩阵
数据结构·算法
LYFlied36 分钟前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
Ven%1 小时前
【AI大模型算法工程师面试题解析与技术思考】
人工智能·python·算法
天勤量化大唯粉1 小时前
枢轴点反转策略在铜期货中的量化应用指南(附天勤量化代码)
ide·python·算法·机器学习·github·开源软件·程序员创富
爱学习的小仙女!1 小时前
算法效率的度量 时间复杂度 空间复杂度
数据结构·算法
AndrewHZ1 小时前
【复杂网络分析】什么是图神经网络?
人工智能·深度学习·神经网络·算法·图神经网络·复杂网络
Swizard1 小时前
拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
python·算法·ai·训练
fab 在逃TDPIE2 小时前
Sentaurus TCAD 仿真教程(十)
算法