一、题目原题重现
字母异位词分组是LeetCode哈希表应用的经典高频题,也是程序员面试必考算法题型,核心考察哈希表键值设计、字符串特征提取、分组逻辑实现,难度适中,非常适合巩固哈希表实战用法,夯实算法基础。
1.1 题目原文
给你一个字符串数组strs,请你将字母异位词组合在一起,可以按任意顺序返回结果列表。
字母异位词定义:由重新排列源单词的字母得到的新单词,且所有字母的出现次数完全相同(例如 “eat” 和 “tea” 互为字母异位词),两个单词可通过重排互相得到。
1.2 题目约束
数组长度范围:
1 <= strs.length <= 10⁴单个字符串长度:
0 <= strs[i].length <= 100字符串仅包含小写英文字母
1.3 示例演示
示例演示示例 1 输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] 输出: [["bat"],["nat","tan"],["ate","eat","tea"]] 示例 2 输入: strs = [""] 输出: [[""]] 示例 3 输入: strs = ["a"] 输出: [["a"]]二、解题思路全解析
2.1 核心问题拆解
解决字母异位词分组的核心关键:找到字母异位词的统一唯一特征标识,让同一组异位词拥有相同标识,不同组标识完全区分,再借助哈希表完成分组,这也是同类分组题型的通用解题逻辑。基于该核心,本题有两种主流解法,分别适配入门理解和最优答题场景。
2.2 解法一:排序法(直观基础解法)
核心思路
互为字母异位词的字符串,排序后得到的结果完全一致(例如 “eat” 和 “tea” 排序后均为 “aet”),利用这一特性,将排序后的字符串作为哈希表唯一键,实现分组存储。
初始化Python字典作为哈希表,键为排序后的字符串,值为对应字母异位词列表
遍历数组内每个字符串,对字符串排序并拼接,生成特征键
将原字符串追加到哈希表对应键的列表中,键不存在则初始化空列表
遍历完成后,将哈希表的值转为列表,即为最终分组结果
代码实现(Python)
解法一:排序法代码from typing import List class Solution: def groupAnagrams(self, strs: List[str]) -> List[List[str]]: # 哈希表:key=排序后字符串,value=对应字母异位词列表 anagram_map = {} for s in strs: # sorted返回字符列表,列表不可哈希,需用join拼接为字符串作为键 key = ''.join(sorted(s)) # 键不存在则初始化空列表,存在则直接追加当前字符串 if key not in anagram_map: anagram_map[key] = [] anagram_map[key].append(s) # 字典values为视图对象,强转列表后符合题目二维列表输出格式 return list(anagram_map.values()) # 本地测试验证 if __name__ == "__main__": sol = Solution() print(sol.groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])) print(sol.groupAnagrams([""])) print(sol.groupAnagrams(["a"]))复杂度分析
时间复杂度:O ( n k log k ) O(nk\log k)O(nklogk),其中n nn为字符串数组长度,k kk为单个字符串的最大长度;对每个字符串执行排序的耗时为O ( k log k ) O(k\log k)O(klogk),共遍历n nn个字符串
空间复杂度:O ( n k ) O(nk)O(nk),额外空间主要用于哈希表存储所有字符串字符,不计返回结果本身的空间开销,属于必要空间消耗
优缺点
✅ 优点:思路直观易懂,代码极简,无复杂逻辑,新手极易上手,适合理解核心分组逻辑
❌ 缺点:排序操作有额外耗时,处理超长字符串时效率偏低
2.3 解法二:字符计数法(最优解,面试首选)
核心思路
字母异位词的每个字符出现次数完全相同,利用这一本质特征,统计26个小写字母的出现次数,生成计数特征作为哈希表键,跳过排序步骤,时间复杂度优化至线性级别,是本题最优解法。
每个字符串对应初始化长度为26的计数列表,对应a-z,初始值全0,索引从0开始依次对应a到z
遍历字符串字符,通过ord()计算字符对应索引,统计每个字母出现次数
Python中列表不可哈希,无法直接作为字典键,需转换为元组作为哈希表唯一特征键
按键分组存储,最后将哈希表值转为列表返回
代码实现(Python)
解法二:字符计数法(最优解)from typing import List class Solution: def groupAnagrams(self, strs: List[str]) -> List[List[str]]: # 哈希表:key=字符计数元组,value=字母异位词列表 anagram_map = {} for s in strs: # 初始化26位字母计数数组,索引0~25分别对应小写字母a~z count = [0] * 26 # 遍历字符,统计每个字母出现次数 for char in s: # 计算字符对应索引,从0开始计数,保证小写字母映射无偏差 idx = ord(char) - ord('a') count[idx] += 1 # 列表不可哈希,转为元组后作为字典键 key = tuple(count) # 键不存在则初始化,存在则追加 if key not in anagram_map: anagram_map[key] = [] anagram_map[key].append(s) return list(anagram_map.values()) # 进阶简化写法(可选,用defaultdict省去判空逻辑) # from collections import defaultdict # class Solution: # def groupAnagrams(self, strs: List[str]) -> List[List[str]]: # anagram_map = defaultdict(list) # for s in strs: # count = [0]*26 # for c in s: # count[ord(c)-ord('a')] +=1 # anagram_map[tuple(count)].append(s) # return list(anagram_map.values()) # 本地测试 if __name__ == "__main__": sol = Solution() print(sol.groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))执行过程演示(以 “eat” 为例,索引从0开始计数)
初始化计数列表:
[0,0,0,...,0](共26个0,对应a-z)字符 ‘e’ 对应索引:
ord('e')-ord('a')=4→count[4] += 1→[0,0,0,0,1,0,...0]字符 ‘a’ 对应索引:
ord('a')-ord('a')=0→count[0] += 1→[1,0,0,0,1,0,...0]字符 ‘t’ 对应索引:
ord('t')-ord('a')=19→count[19] += 1→[1,0,0,0,1,0,...,1,...0]转换为元组作为键,“tea” 和 “ate” 会生成完全相同的键,自动归为一组
复杂度分析
时间复杂度:O ( n k ) O(nk)O(nk),仅需线性遍历数组所有字符,无排序等高耗时操作,遍历效率拉满
空间复杂度:O ( n k ) O(nk)O(nk),额外开销来自哈希表存储和固定大小的计数数组,不计返回结果空间,消耗合理
优缺点
✅ 优点:时间最优,无排序开销,全场景适配,稳定性极强
✅ 优点:特征键唯一性拉满,完全规避排序潜在问题,面试首选
❌ 缺点:代码逻辑稍复杂,需理解字符编码与计数转换逻辑
三、核心注意事项与易错点
空字符串处理:空字符串无字符,计数列表全0,排序后为空串,可正常单独分组,无需额外判断
哈希键类型禁忌:Python列表不可作为字典键,计数列表必须转元组,排序后字符列表必须用join拼接为字符串,否则代码直接报错
字符索引计算:通过
ord(char)-ord('a')映射0-25索引,严格对应小写字母,无索引越界风险返回格式规范:字典values()为视图对象,必须用list()强转,才能得到题目要求的二维列表格式
四、两种解法性能对比
| 解法类型 | 时间复杂度 | 空间复杂度 | 适用场景 | 推荐指数 |
|---|---|---|---|---|
| 排序法 | O ( n k log k ) O(nk\log k)O(nklogk) | O ( n k ) O(nk)O(nk) | 字符串较短、追求代码简洁、快速入门 | ⭐⭐⭐⭐ |
| 字符计数法 | O ( n k ) O(nk)O(nk) | O ( n k ) O(nk)O(nk) | 全场景、面试答题、大规模数据处理 | ⭐⭐⭐⭐⭐ |
五、全文总结与思考延伸
5.1 全文总结
本题核心考点:哈希表键值设计、字符串特征提取、分组算法实现,是哈希表实战的经典入门题型;
排序法适合入门理解,代码简洁但时间复杂度偏高,适合小规模数据场景;
字符计数法是本题最优解,通过空间换时间,时间复杂度优化至O(nk),面试务必优先使用;
同类分组题型均可借鉴该思路:提取统一特征作为键,借助哈希表实现高效分组,通用性极强。
5.2 思考延伸
通用场景适配:若字符串包含Unicode字符而非仅小写字母,排序法仍通用(直接按字符排序);计数法可改用
collections.Counter统计字符频次,再转为元组作为键。代码进阶写法:Python中可使用
defaultdict(list)省去键不存在的判空逻辑,简化代码,适合熟练后使用,基础学习阶段保留显式判断更易理解。
刷题延伸:吃透本题后,可练习 LeetCode 242.有效的字母异位词(基础前置题)、LeetCode 438.找到字符串中所有字母异位词(进阶题),巩固字符计数与哈希表用法。