字母异位词分组
字数 2421 2025-12-14 23:00:28
字母异位词分组
题目描述
给你一个字符串数组 strs,请你将字母异位词组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
解题过程
步骤 1:理解核心问题
字母异位词的定义是关键:两个字符串互为字母异位词,当且仅当它们包含的字母种类和每个字母的数量完全一致,仅仅是排列顺序不同。例如,“eat“、”tea“、”ate“是字母异位词,因为它们都包含1个e、1个a、1个t。
目标:将所有互为字母异位词的字符串分到同一个组中。
步骤 2:寻找分组依据(哈希的“键”)
我们需要找到一个“签名”或“特征”,这个特征对于同一组字母异位词是唯一且相同的,对于不同组的字母异位词是不同的。这样我们就可以用这个特征作为哈希表的键(Key),将原字符串作为该键对应的值列表(Value)中的元素。
如何计算这个特征?
方案 1:排序字符串
- 思路:既然字母异位词排序后结果相同,可以将排序后的字符串作为“键”。
- 操作:对每个字符串
s,将其字符排序得到s_sorted。例如:"eat"-> 排序 ->"aet""tea"-> 排序 ->"aet"
- 那么
"aet"就成为了["eat", "tea"]这一组的键。 - 时间复杂度:排序每个字符串,假设字符串最大长度为
K,排序一个字符串的时间复杂度是O(K log K)。对于N个字符串,总时间复杂度为O(N * K log K)。
方案 2:计数编码
- 思路:统计每个字符串中26个字母出现的次数,然后将统计结果编码成一个唯一的字符串(例如,用
#分隔计数)作为键。 - 操作:创建一个长度为26的数组
count,记录每个字母出现的次数。然后生成一个如"#1#0#0...#1#0#0"的字符串(其中数字代表该位置字母出现的次数)。 - 例如:
"eat"的计数是[a:1, e:1, t:1],编码为"#1#1#0#0...#1..."(位置对应a, e, t...)。 - 时间复杂度:需要遍历每个字符串的每个字符来计数,
O(N * K),然后将计数数组编码为字符串(固定26次操作),总时间复杂度为O(N * K)。当K较大时,此方法可能比排序法更优。
步骤 3:选择哈希表结构
我们使用一个哈希表(字典/映射),其结构为:Map<Key, List<String>>。
Key:我们计算出的特征(排序后的字符串 或 计数编码字符串)。Value:一个列表,用于存放所有具有相同Key的原字符串。
步骤 4:详细步骤(以排序法为例)
- 初始化一个空的哈希表
Map<String, List<String>> map = new HashMap<>();。 - 遍历输入字符串数组
strs中的每个字符串s:
a. 将当前字符串s转换为字符数组:char[] chars = s.toCharArray();
b. 对字符数组进行排序:Arrays.sort(chars);
c. 将排序后的字符数组转换回字符串,作为键:String key = new String(chars);
d. 在哈希表map中查找这个key:- 如果
map中不存在这个key,则创建一个新的空列表,并将(key, 新列表)存入map。 - 从
map中获取这个key对应的列表list。
e. 将原始字符串s添加到这个列表list中。
- 如果
- 遍历结束后,
map中的所有值(即那些列表)就是我们需要的结果分组。 - 将
map中的所有值收集到一个新的列表List<List<String>> result中并返回。
步骤 5:举例说明
输入:strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
过程:
- 处理
"eat":排序后键为"aet",map中加入"aet" -> ["eat"]。 - 处理
"tea":排序后键为"aet",找到已有列表,加入,变为"aet" -> ["eat", "tea"]。 - 处理
"tan":排序后键为"ant",map中加入"ant" -> ["tan"]。 - 处理
"ate":排序后键为"aet",加入列表,变为"aet" -> ["eat", "tea", "ate"]。 - 处理
"nat":排序后键为"ant",加入列表,变为"ant" -> ["tan", "nat"]。 - 处理
"bat":排序后键为"abt",map中加入"abt" -> ["bat"]。
最终结果:map 的值列表为 [["eat","tea","ate"], ["tan","nat"], ["bat"]],顺序不限。
步骤 6:复杂度分析
- 时间复杂度:
O(N * K log K),其中N是strs的长度,K是strs中字符串的最大长度。主要开销在于对每个字符串排序。 - 空间复杂度:
O(N * K),哈希表需要存储所有字符串(原始字符串和键,但键的总长度不超过所有字符串的总长度)。
步骤 7:关键点总结
- 哈希思想:将看似复杂的“异位词”判定问题,转化为一个寻找“唯一、稳定特征”的问题,利用哈希表进行高效分组。
- 键的设计:排序字符串是最直观的键;计数编码是另一种高效的键生成方式,尤其在字符串较长时可能更有优势。
- 映射结构:哈希表的值是一个列表,用于动态收集同一组的元素。