652. 重复子树查找,一些python的奇巧淫技

652. Find Duplicate Subtrees

给定root,查找树内的重复子树,返回所有重复子树列表,列表去重

一些有意思的东西

一个问题引出了很多有意思的东西。

  • 元组序列化,python中可以方便使用的技巧,和string序列化互补
  • 性能测试,timeit工具包
  • 对象编号,借用dict工具对复杂对象编号,常用的技巧
  • defaultdict的使用,一个好用的字典
  •  frozenset和tuple的hash原理,前者是缓存了hash,后者是计算,和元素的内容有关,无缓存

v 1

思路:

后序遍历描述树的样子(满树后序唯一),map记录其他树的样子
-
时间复杂度:

递归树深度与树的平衡性有关,最差情况下深度为N,对于每一层的所有分支,算法复杂度为O(N)——来自hash函数计算,它和子树的节点数量有关,左右子树结合和为N。所以,这个算法是明显的O(N^2)算法。理想情况下,我们可以期待它O(logN N)。
-
空间复杂度:

N个节点进入dict,O(N)
-
节点。

""" 
version 1
-
运行时间:
 
Runtime: 52 ms, faster than 81.61% of Python online submissions for Find Duplicate Subtrees.
Memory Usage: 23.2 MB, less than 62.54% of Python online submissions for Find Duplicate Subtrees.
 
"""
 class Solution(object):
    def findDuplicateSubtrees(self, root):
        sel, vis = {}, set()
        def trav(root):
            if not root: return '#'
            left = trav(root.left)
            right = trav(root.right)
            cur = left + ',' + right + ',' + str(root.val)
            if cur in vis:
                sel[cur] = root
            else:
                vis.add(cur)
            return cur
        trav(root)
        return list(sel.values())

v2

思路:

改进version 1提到的重复hash问题。这个问题有点意思,比较容易绕晕,其实核心是,子树的hash值能否缓存,直接作为该节点的hash值的计算——这样就不用每次都计算子树hash了。
对比下面两个版本,tuple实现和frozenset实现,核心的区别是,frozenset会缓存hash值,而它的hash值计算是取出所有元素的hash值进行计算,而tuple的hash和元素内容相关,没有缓存。可以参考frozenset的hash实现。
-
空间复杂度
关于该版本的空间复杂度,由于dict需要保存每一个子树的所有节点,对于每一层所有节点子树节点的和为N,因而又是和树的深度有关,最差O(N^2),否则O(N logN)
-
时间复杂度
看起来每次都能在O(1)的时间内计算完成hash值,但隐藏的隐患来自hash函数本身,它只是摊还分析意义上的O(1),在极端情况下,不断出现hash冲突,则还是O(N)

重复hash

效率低的原因之一是hash重复计算,例如对于root,即使子树的hash已经计算完成,仍然需要计算整颗数的所有节点

'''
version 2
-
-
ref: 
version2 源码,StefanPochmann的大佬,分析非常精彩。 https://leetcode.com/problems/find-duplicate-subtrees/discuss/106016/O(n)-time-and-space-lots-of-analysis
frozenset hash函数:https://stackoverflow.com/questions/20832279/python-frozenset-hashing-algorithm-implementation
'''
class Solution(object):
    def findDuplicateSubtrees(self, root):
        def tuplify(root):
            if root:
                tuple = root.val, tuplify(root.left), tuplify(root.right)
                trees[tuple].append(root)
                return tuple
        trees = collections.defaultdict(list)
        tuplify(root)
        return [roots[0] for roots in trees.values() if roots[1:]]
 
 
class Solution(object):
    def findDuplicateSubtrees(self, root):
        def convert(root):
            if root:
                result = frozenset([(root.val, convert(root.left), convert(root.right))])
                trees[result].append(root)
                return result
        trees = collections.defaultdict(list)
        convert(root)
        return [roots[0] for roots in trees.values() if roots[1:]]

V3

思路
解决上述问题,其实有个常用的技巧,编号。以下的实现极为精彩。

'''
version 3
-
ref:
https://leetcode.com/problems/find-duplicate-subtrees/discuss/106016/O(n)-time-and-space-lots-of-analysis
'''
    class Solution(object):
    def findDuplicateSubtrees(self, root, heights=[]):
        def getid(root):
            if root:
                id = treeid[root.val, getid(root.left), getid(root.right)]
                trees[id].append(root)
                return id
        trees = collections.defaultdict(list)
        # 不声明默认值
        treeid = collections.defaultdict()
        # 将它绑定它自己的size函数,每次添加元素的时候,默认等于一个新的编号
        treeid.default_factory = treeid.__len__
        getid(root)
        return [roots[0] for roots in trees.values() if roots[1:]]

 

REF

  • version2 3 源码,StefanPochmann,分析得尤其精彩。 https://leetcode.com/problems/find-duplicate-subtrees/discuss/106016/O(n)-time-and-space-lots-of-analysis
  • frozenset hash函数:https://stackoverflow.com/questions/20832279/python-frozenset-hashing-algorithm-implementation
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值