在很多应用中,我们会发现某种映射关系(模式),但它并不是简单一 一对应的。这时,我们就要从键 key 入手,通过设计合适的键,建立映射关系。leetbook的这个章节总结了一些常见的键,以供参考。
49. 字母异位词分组
class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:str_dict = dict()for word in strs:s_word = str(sorted(word))if s_word in str_dict:str_dict[s_word].append(word)else:str_dict[s_word] = [word]ans = []for value in str_dict.values():ans.append(value)return ans
对于有相同字母但是位置不同的单词(字母异位词),可知它们的模式(共通点)就是字母构成相同,所以只需要以固定的顺序比较这些单词(排序),即可发现这个模式。注意 sorted(word) 得到的是列表,不能作为 key,应该将其转为字符串 str 后再作为 key。
249. 移位字符串分组
class Solution:def groupStrings(self, strings: List[str]) -> List[List[str]] :hashtable = defaultdict(list) # 遇到不存在的key不会报错的dictfor s in strings :if s[0] == 'a':hashtable[s].append(s)else:key = list(s)key[0] = 'a'diff = ord(s[0]) - ord('a')for i in range(1, len(s)): key[i] = chr(ord(s[i]) - diff) if ord(s[i]) - diff >= ord('a') else chr(ord(s[i]) - diff + 26)key = ''.join(key)hashtable[key].append(s)ans = []for mode, sublist in hashtable.items():ans.append(sublist)return ans
此处用到了 collections.defaultdict
,顾名思义,就是有默认值的 dict,因此遇到不存在的 key 不会报 KeyError 错误。对移位字符串进行分组,我们可以用每个组的第一个字符串,也就是首字母为 ‘a’ 的字符串作为该组的代表(键),因为这个字符串包含了该组的字符串长度的信息与各字母偏移的信息,同组的其余字符串只不过是相对它有一定的偏移而已。因此,对于一个字符串,首先将其首字母变成 ‘a’,记录偏移量 diff,后面的字母都按照 diff 进行偏移,若越界则加 26 循环回来,得到这个字符串对应的键并加入到字典中。
36. 有效的数独
class Solution:def isValidSudoku(self, board: List[List[str]]) -> bool:rows = collections.defaultdict(list)cols = collections.defaultdict(list)squares = collections.defaultdict(list)for i in range(len(board)):for j in range(len(board[0])):if board[i][j] == '.':continueif board[i][j] in rows[i] or board[i][j] in cols[j] or board[i][j] in squares[(i//3, j//3)]:return Falseelse:rows[i].append(board[i][j])cols[j].append(board[i][j])squares[(i//3, j//3)].append(board[i][j])return True
数独中的行、列、3x3宫都是一种模式,相同行、列的元素显然它的行索引 i 和列索引 j 是一样的,那同一个3x3宫的元素呢?可以发现,元素的行或者列整除3的结果,表示了元素从行或者列数起的第几个宫内,所以可以使用(i // 3, j // 3)作为3x3宫的模式。
652. 寻找重复的子树
class Solution():def findDuplicateSubtrees(self, root):trees = collections.defaultdict()trees.default_factory = trees.__len__ #当尝试查找字典中不存在的键时,会创建一个条目,其值等于字典中的项目数count = collections.Counter()ans = []def lookup(node):if node:uid = trees[node.val, lookup(node.left), lookup(node.right)] # 前序遍历count[uid] += 1if count[uid] == 2:ans.append(node)return uidlookup(root)return ans
trees.default_factory = trees.__len__
是一个小技巧,讲解在这里。在这里我们用子树的(根节点值、左子树uid、右子树uid)作为 uid,唯一标识这个子树。只有当两个子树的 uid 相同时,认为这两个子树是相同的。