【LeetCode 热题 100】49. 字母异位词分组
Problem: 49. 字母异位词分组
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N * K log K)
- 空间复杂度:O(N * K)
整体思路
这段代码旨在解决一个经典的字符串问题:字母异位词分组 (Group Anagrams)。问题要求将一个字符串数组中的所有字符串,按照它们是否互为字母异位词(Anagrams)来进行分组。字母异位词是指由相同的字符以不同的顺序构成的字符串(例如,"eat"
和 "tea"
)。
该算法采用了一种非常经典且高效的策略:使用排序后的字符串作为“规范哈希键”。其核心逻辑步骤如下:
-
核心思想:异位词的“身份证”
- 算法的基石在于一个关键的观察:所有互为字母异位词的字符串,当它们内部的字符被排序后,都会得到完全相同的字符串。
- 例如,
"eat"
,"tea"
,"ate"
这三个异位词,排序后都变成了"aet"
。 - 因此,这个排序后的字符串
"aet"
就可以作为这一组异位词的唯一标识,或者说“身份证”。
-
数据结构选择
- 算法选择了一个 哈希表 (HashMap),
Map<String, List<String>>
,来存储分组结果。 - 键 (Key):哈希表的键是排序后的“规范字符串”(如
"aet"
)。 - 值 (Value):哈希表的值是一个字符串列表,用来存放所有共享同一个“规范字符串”的原始字符串(如
["eat", "tea", "ate"]
)。
- 算法选择了一个 哈希表 (HashMap),
-
算法步骤
- 遍历输入数组:算法遍历输入的字符串数组
strs
中的每一个字符串s
。 - 生成哈希键:对于每个字符串
s
:
a. 将其转换为字符数组char[] sortedS = s.toCharArray();
。
b. 对这个字符数组进行排序Arrays.sort(sortedS);
。
c. 将排序后的字符数组转换回字符串new String(sortedS)
,这就是该字符串的“规范哈希键”。 - 分组:
a. 使用这个“规范哈希键”去哈希表中查找。
b.m.computeIfAbsent(key, _ -> new ArrayList<>())
是一个非常简洁的Java 8+的写法。它的作用是:如果key
不存在,就创建一个新的ArrayList
并与key
关联;如果key
已存在,就直接返回与key
关联的现有ArrayList
。
c..add(s)
:无论上面是新建还是获取,最终都会将原始字符串s
添加到对应的列表中。 - 返回结果:遍历结束后,哈希表
m
的values()
集合中就包含了所有分组好的列表。将m.values()
转换为一个新的ArrayList
并返回。
- 遍历输入数组:算法遍历输入的字符串数组
完整代码
import java.util.*;class Solution {/*** 将字符串数组按照字母异位词进行分组。* @param strs 输入的字符串数组* @return 分组后的字符串列表*/public List<List<String>> groupAnagrams(String[] strs) {// 创建一个哈希表,键是排序后的规范字符串,值是属于该组的原始字符串列表。Map<String, List<String>> m = new HashMap<>();// 遍历输入的每一个字符串for (String s : strs) {// 步骤 1: 生成“规范哈希键”// 将当前字符串转换为字符数组char[] sortedS = s.toCharArray();// 对字符数组进行排序Arrays.sort(sortedS);// 将排序后的字符数组转换回字符串,作为哈希表的键String key = new String(sortedS);// 步骤 2: 将原始字符串放入对应的分组// computeIfAbsent(key, k -> new ArrayList<>()) 的语义:// - 如果 map 中不存在 key,则执行 lambda 表达式创建一个 new ArrayList<>(),// 并将其作为 value 与 key 关联,然后返回这个新创建的 list。// - 如果 map 中已存在 key,则直接返回与 key 关联的现有 list。// 接着,.add(s) 将原始字符串 s 加入到获取到的 list 中。m.computeIfAbsent(key, _ -> new ArrayList<>()).add(s);}// 步骤 3: 返回结果// m.values() 返回哈希表中所有值的集合(即所有分组的 List<String>)。// 将这个集合转换为一个新的 ArrayList 并返回。return new ArrayList<>(m.values());}
}
时空复杂度
时间复杂度:O(N * K log K)
- 变量定义:
N
:输入数组strs
中字符串的数量。K
:数组中字符串的最大长度。
- 外层循环:
for (String s : strs)
循环执行N
次。 - 循环内部操作:这是分析的关键。
s.toCharArray()
:将长度为k
的字符串转为字符数组,时间复杂度为 O(k)。Arrays.sort(sortedS)
:对一个长度为k
的字符数组进行排序,时间复杂度为 O(k log k)。new String(sortedS)
:将长度为k
的字符数组转为字符串,时间复杂度为 O(k)。m.computeIfAbsent(...)
和add(...)
:哈希表的插入和获取操作,平均时间复杂度是 O(k),因为哈希键(字符串)的比较和哈希码计算的成本与字符串长度k
成正比。
综合分析:
每次循环的成本由其中最耗时的操作决定,即排序操作 O(k log k)。
由于这个操作在外层循环中重复了 N
次,所以总的时间复杂度是 O(N * K log K)。
空间复杂度:O(N * K)
- 主要存储开销:哈希表
m
是主要的额外空间消耗。 - 空间大小:
- 在最坏的情况下,如果所有字符串都互不为异位词,那么哈希表中将有
N
个条目。 - 每个条目的键是一个排序后的字符串,其长度最多为
K
。 - 每个条目的值是一个列表,其中包含原始字符串,其长度也最多为
K
。 - 因此,哈希表需要存储所有
N
个字符串(以原始形式或排序形式)。所有字符串的总长度可以表示为N * K_avg
(K_avg
为平均长度),在最坏情况下为 O(N * K)。
- 在最坏的情况下,如果所有字符串都互不为异位词,那么哈希表中将有
- 临时变量:在循环中,
char[] sortedS
需要 O(K) 的临时空间。但这部分空间在每次循环后都会被回收,并且其最大值 O(K) 不会超过哈希表的总空间 O(N*K)。
综合分析:
算法所需的额外空间主要由哈希表决定,它存储了所有原始字符串的信息。因此,空间复杂度为 O(N * K)。
参考灵神