要理解这行代码,我们需要先结合它的应用场景 ------LeetCode 字母异位词分组问题(将由相同字母组成、仅顺序不同的字符串分到同一组,如 "eat" 和 "tea" 为一组),再拆解代码的核心逻辑、关键 API 特性和背后的设计思路:
一、先明确场景背景
字母异位词的核心特征是:排序后会得到完全相同的字符串。例如:
- "eat" → 排序后 → "aet"
- "tea" → 排序后 → "aet"
- "tan" → 排序后 → "ant"
因此,我们可以用「排序后的字符串」作为分组的 key,用「存储原字符串的列表」作为分组的 value,通过 HashMap 实现快速分组。而你贴的代码,正是这一逻辑的「简洁实现核心」。
二、逐部分拆解代码
代码:m.computeIfAbsent(new String(sortedS), _ -> new ArrayList<>()).add(s);其中 m 是 HashMap<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 下。 - char 数组的
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"
sortedS1 = {'a','e','t'}→key1 = new String(sortedS1) = "aet";- 检查
m中是否有 "aet"?无; - 执行 Lambda 生成空列表
new ArrayList<>(),将"aet" → []存入m; - 返回这个空列表,调用
add("eat")→ 列表变成["eat"]; - 此时
m = {"aet": ["eat"]}。
步骤 2:处理 s2 = "tea"
sortedS2 = {'a','e','t'}→key2 = "aet";- 检查
m中是否有 "aet"?有,对应列表["eat"]; - 直接返回该列表,调用
add("tea")→ 列表变成["eat", "tea"]; - 此时
m = {"aet": ["eat", "tea"]}。
步骤 3:处理 s3 = "tan"
sortedS3 = {'a','n','t'}→key3 = "ant";- 检查
m中是否有 "ant"?无; - 生成空列表,存入
m为"ant": []; - 调用
add("tan")→ 列表变成["tan"]; - 最终
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());
}
}
关键步骤拆解:
-
哈希表初始化
Map<String, List<String>> m = new HashMap<>():定义一个哈希表,键(key)是 "排序后的字符串"(作为异位词的统一标识),值(value)是List<String>(存储所有属于该组的原字符串)。 -
遍历字符串并处理 对每个原字符串
s:char[] sortedS = s.toCharArray():将字符串转为字符数组(因为字符串是不可变的,而数组可以排序)。Arrays.sort(sortedS):对字符数组排序(例如 "tea" → 排序后字符数组为['a','e','t'])。new String(sortedS):将排序后的字符数组转回字符串(作为哈希表的 key)。这里必须转成 String,因为char [] 不能作为 HashMap 的 key (数组的equals是引用比较,而 String 的equals是内容比较,确保异位词的 key 相同)。
-
核心 API:
computeIfAbsentm.computeIfAbsent(key, mappingFunction).add(s):- 作用:检查哈希表中是否存在
key。若不存在,执行mappingFunction(这里是创建空列表)并存入哈希表;最后返回key对应的列表。 - 效果:一行代码完成 "判断 key 是否存在→不存在则创建列表→将原字符串加入列表" 的操作,简化逻辑。
- 作用:检查哈希表中是否存在
-
返回结果
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方法的使用简化了代码,是哈希表操作的高频技巧,也是面试中的常见考点。
优化方向?