class Solution(object):
def groupAnagrams(self, strs):
"""
:type strs: List[str]
:rtype: List[List[str]]
"""
hash = collections.defaultdict(list)
# solution1: 对字母进行排序 sorted
for str in strs:
hash[tuple(sorted(str))].append(str)
# 记住要转为tuple,因为dict里面的key必须是不可变的
# 或者用字符
# key = "".join(sorted(str))
# hash[key].append(str)
return list(hash.values())
class Solution(object):
def groupAnagrams(self, strs):
"""
:type strs: List[str]
:rtype: List[List[str]]
"""
hash = collections.defaultdict(list)
# solution2: 对字母进行计数 count
for str in strs:
count = [0]*26
for i in str:
count[ord(i) - ord('a')] += 1
hash[tuple(count)].append(str)
return list(hash.values())
让我们逐步分析这段代码:
ord函数
- ord() 函数:
ord() 函数返回一个字符的 Unicode 码点。在这里,它用于将字符转换为数字,以便计算其在 counts 列表中的索引。例如,ord(‘a’) 返回 97,ord(‘b’) 返回 98,依此类推。通过 ord(ch) - ord(“a”),我们将 ‘a’ 映射到 0,‘b’ 映射到 1,以此类推。
list转为tuple
- 将 list 转换为 tuple:
在 Python 中,list 是可变的(mutable),而 dict 的键必须是不可变的(immutable)。tuple 是不可变的,因此可以用作字典的键。这就是为什么我们需要将 counts 列表转换为元组。
solution1的时空复杂度
-
时间复杂度:O(nklogk),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要遍历 n 个字符串,对于每个字符串,需要 O(klogk) 的时间进行排序以及 O(1) 的时间更新哈希表,因此总时间复杂度是 O(nklogk)。
-
空间复杂度:O(nk),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要用哈希表存储全部字符串。
solution2的时间复杂度和空间复杂度分析
时间复杂度分析:
- 外层循环遍历 strs 列表,复杂度为 O(n),其中 n 是 strs 的长度。
- 对于每个字符串 st:
- 创建 counts 列表:O(1)(因为长度固定为 26)
- 遍历 st 中的每个字符:O(k),其中 k 是字符串的长度
- 转换为元组和添加到字典:O(1)
- 最后,list(mp.values()) 的复杂度是 O(n)
总的时间复杂度是 O(n * k),其中 n 是字符串数量,k 是字符串的平均长度。
空间复杂度分析:
- counts 列表:每次循环都创建一个固定长度(26)的列表,O(1)
- mp 字典:存储所有分组后的字符串,最坏情况下 O(n * k)
- 返回的列表:O(n * k)
总的空间复杂度是 O(n * k)。
这种方法比之前使用排序的方法在时间复杂度上更优,因为它避免了排序操作(之前是 O(n * k log k))。这种计数方法特别适用于已知字符集有限(如只包含小写字母)的情况。
时间复杂度:O(n(k+∣Σ∣)),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度,Σ 是字符集,在本题中字符集为所有小写字母,∣Σ∣=26。需要遍历 n 个字符串,对于每个字符串,需要 O(k) 的时间计算每个字母出现的次数,O(∣Σ∣) 的时间生成哈希表的键,以及 O(1) 的时间更新哈希表,因此总时间复杂度是 O(n(k+∣Σ∣))。
空间复杂度:O(n(k+∣Σ∣)),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的最大长度,Σ 是字符集,在本题中字符集为所有小写字母,∣Σ∣=26。需要用哈希表存储全部字符串,而记录每个字符串中每个字母出现次数的数组需要的空间为 O(∣Σ∣),在渐进意义下小于 O(n(k+∣Σ∣)),可以忽略不计
确实,我之前的分析与官方解答有些差异。让我解释一下官方解答的思路,并澄清为什么它可能更准确:
-
时间复杂度:O(n(k+|Σ|))
- n 是字符串数量,k 是最长字符串的长度,|Σ| 是字符集大小(这里是26)。
- 对每个字符串(共n个):
- 计算字母出现次数:O(k)
- 生成哈希表的键(将counts转为tuple):O(|Σ|)
- 更新哈希表:O(1)
这比我之前的分析更精确,因为它考虑了生成哈希表键的时间(转换tuple的过程)。
-
空间复杂度:O(n(k+|Σ|))
- 哈希表存储所有字符串:O(nk)
- 每个键(tuple)的长度:O(|Σ|)
- 总共有n个这样的键值对
这个分析考虑了键(tuple)本身占用的空间,这是我之前忽略的一点。
修正和说明:
-
我之前忽略了将list转为tuple的时间和空间开销。这个过程是O(|Σ|)的,不是O(1)。
-
在空间复杂度中,我没有考虑到哈希表中键(tuple)占用的空间。每个键的长度是O(|Σ|),这也需要计入总空间。
-
官方解答中提到的O(|Σ|)空间用于记录每个字符串中字母出现次数的数组,在渐进意义下确实可以忽略,因为它小于O(n(k+|Σ|))。
-
最大长度k和平均长度的使用:在最坏情况分析中,使用最大长度k更为准确。
总结:官方的分析更加精确和全面,特别是在考虑字符集大小和转换为tuple的开销方面。这种细致的分析对于理解算法的性能特别是在处理大规模数据时非常重要。感谢您指出这一点,这有助于我们更深入地理解算法复杂度分析。