Leetcode Link: 49. 字母异位词分组 - 力扣(LeetCode)

题目

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

本质上就是对给定的字符串数组进行分组

解法一

思路:暴力

  1. 每次取 strs 里的第一个字符串(建议pop(0))
  2. 对其进行计数:Counter()
  3. 遍历 strs,找计数结果一样的,添加到当前组
  4. 将当前组添加到结果中

超时

题解

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        res = []
        while(len(strs)>0):
            new = [strs.pop(0)]   # 当前分组
            cur = Counter(new[0]) # 计数
            j = 0
            while(j < len(strs)): # 逐个遍历
                tmp_counter = Counter(strs[j])
                if tmp_counter == cur:
	                # 有就弹出,并添加到当前分组
                    new.append(strs[j]) 
                    strs.pop(j)
                else:
                    j += 1
            res.append(new)
        return res

解法二

思路: 分析上述问题超时的原因:

  1. 可能是 pop 的次数太多,因为 list 的 pop 操作代价比较大,因此我们考虑避免 pop 操作
  2. 我们遍历 strs 的次数过多,导致浪费

改进:

  1. 创建 res 和 res_counter, 前者用来保存结果,后者用来保存 res 中对应位置(对应组)的 counter
  2. 只遍历一次strs,取出一个s,制作s_counter, 遍历res_counter,找不到则按照新来的处理,找到了则添加到对应的res分组中去

题解:依然超时…

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        res = []
        res_counter = []
        for i, s in enumerate(strs):
            s_counter = Counter(s) # 取出当前的s
 
            flag = 0
            for j, tar_counter in enumerate(res_counter):
                if s_counter == tar_counter:
                    res[j].append(s)
                    flag = 1
                    break # 找到了就不用看了
 
            if flag == 0:
                res.append([s])
                res_counter.append(s_counter)
            
        return res

解法三

思路:可能是遍历的 res_counter 次数太多

在这种思路下,我们无论如何都要在在 res_counter 中看一下,因此思考如何减少“看”的次数

由于这是字母,counter 可以用一个 [1,26] 的列表来制作,进而避免使用Counter(),对应位置保存的是对应字母的出现顺序,然后多添加一位(27 位),用来保存这个 counter 对应的字符串的长度,并利用长度进行分组和排序,减少”看”的次数

这样就会出现另一个问题,利用长度分组了,怎么找到对应长度分组里的某一个计数对应 res 哪一个字符串? 可以再补充一个位置(第 28 位),用来保存对应 res 的位置(类似解法二中 res_counterres 的关系是一一对应的,这里 res_counterres 关系由 res_counter 中第 28 位 (res_counter[27] 指定))

题解

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        res_counter =  [[] for _ in range(100)] # 保存对应长度的counter,每个元素是一个列表
        res = []
        for i, s in enumerate(strs):
            # [0~25] 字母计数, [26] 长度, [27] 对应res的索引,在主函数里写
            s_counter = self.get_counter(s)  
 
            flag = 0
            cur_len = s_counter[26]
            for j, tar_counter in enumerate(res_counter[cur_len]): # 只在对应长度的组里去找
	            # 看看counter是不是相等,只考虑前26位
                if s_counter[:26] == tar_counter[:26]: 
                    res[tar_counter[27]].append(s)
                    flag = 1
                    break
 
			# 若没有找到目标,则需要执行三步
			# 1. 结果res中 s 独立为新的一组
			# 2. 将res_counter中的27位指定为对应res的位置
			# 3. 在对应长度的分组中添加s_counter.
            if flag == 0:
                res.append([s])
                s_counter[27] = len(res)-1
                res_counter[cur_len].append(s_counter)
            
        return res
 
        
    def get_counter(self, s):
        counter = [0]*28 # [0~25] 字母计数, [26] 长度, [27] 对应res的索引,在主函数里写
        for c in s:
            idx = ord(c) - ord('a')
            counter[idx] += 1
        counter[26] = len(s)
        return counter

解法四

思路 主要是 dict.get() 函数的理解

dict.get(key, default=None)
  1. key — 字典中要查找的键。
  2. default — 如果指定键的值不存在时,返回该默认值。

还有一点要注意:因为字典的键,必须是不可变类型,所以用 tuple

题解

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        dict = {}
        for item in strs:
            key = tuple(sorted(item))
            # a1 = [123] a2 = [234] a1+a2 = [123, 234]
            dict[key] = dict.get(key, []) + [item]
        return list(dict.values())

Attention

  • 两个列表相加 = append
  • 同理可以直接操作 dict.keys()

启发和联系