哈希表(散列表)基础概念与经典题目(Leetcode题解-Python语言)之下——设计键

在很多应用中,我们会发现某种映射关系(模式),但它并不是简单一 一对应的。这时,我们就要从键 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不会报错的dict
        for 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] == '.':
                    continue
                if board[i][j] in rows[i] or board[i][j] in cols[j] or board[i][j] in squares[(i//3, j//3)]:
                    return False
                else:
                    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] += 1
                if count[uid] == 2:
                    ans.append(node)
                return uid

        lookup(root)
        return ans

trees.default_factory = trees.__len__是一个小技巧,讲解在这里。在这里我们用子树的(根节点值、左子树uid、右子树uid)作为 uid,唯一标识这个子树。只有当两个子树的 uid 相同时,认为这两个子树是相同的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值