leetCode(二叉树)

二叉树专题(持续更新)

LeetCode617.合并二叉树

题目概要:
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
在这里插入图片描述
此题比较简单,直接递归结束,简单题。

class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:

        if not t1 or not t2:
            return t1 or t2
        t1.val = t1.val + t2.val
        t1.left = self.mergeTrees(t1.left, t2.left)
        t1.right = self.mergeTrees(t1.right, t2.right)
        return t1

235. 二叉搜索树的最近公共祖先

思路详解,根据二叉搜索树的特性,找到从根节点到指定节点的路径并存储下来,最近公共祖先节点且要求深度最大,既是两条路径最后分叉的那个节点即时公共几点(也可以说是最后一个公共节点)。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        #  最近公共祖先,首先
        def getPath(root: TreeNode, target: TreeNode) -> List[TreeNode]:
            path = list()
            node = root
            while node != target:
                path.append(node)
                if target.val < node.val:
                    node = node.left
                else:
                    node = node.right
            path.append(node)
            return path
        
        path_p = getPath(root, p)
        path_q = getPath(root, q)
        ancestor = None
        for u, v in zip(path_p, path_q):
            if u == v:
                ancestor = u
            else:
                break
        
        return ancestor

题解一种使用了两次遍历,试试节省遍历次数,一次遍历得到答案
我们从根节点开始遍历;

  • 如果当前节点的值大于 p和 q 的值,说明 p和 q 应该在当前节点的左子树,因此将当前节点移动到它的左子节点

  • 如果当前节点的值小于 p 和 q 的值,说明 p 和 q 应该在当前节点的右子树,因此将当前节点移动到它的右子节点

  • 如果当前节点的值不满足上述两条要求,那么说明当前节点就是「分岔点」。此时,p和 q要么在当前节点的不同的子树中,要么其中一个就是当前节点。

可以发现,如果我们将这两个节点放在一起遍历,我们就省去了存储路径需要的空间。

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        ancestor = root
        while True:
            if p.val < ancestor.val and q.val < ancestor.val:
                ancestor = ancestor.left
            elif p.val > ancestor.val and q.val > ancestor.val:
                ancestor = ancestor.right
            else:
                break
        return ancestor

二叉树的先序,中序,后序遍历

二叉树的这三种遍历使用递归方法的话,基本都是一个模板,非常容易实现。代码如下

# 递归方法,前中后 第一种
res = []
def dfs(root):
    if not root:
        return   
    dfs(root.left)  #先序
    res.append(root.val) # 中序
    dfs(root.right)  # 后序
dfs(root)
return res

时间复杂度:O(n)O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。

空间复杂度:O(n)O(n),为递归过程中栈的开销,平均情况下为 O(\log n)O(logn),最坏情况下树呈现链状,为 O(n)O(n)。

  • 迭代法,和递归的开销在本题上相查不大。只是显示的实现了一个栈出来
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return list()
        
        res = list()
        stack = list()
        prev = None

        while root or stack:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()
            if not root.right or root.right == prev:
                res.append(root.val)
                prev = root
                root = None
            else:
                stack.append(root)
                root = root.right
        
        return res

两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同。

时间复杂度:O(n)O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n)O(n),为迭代过程中显式栈的开销,平均情况下为 O(\log n)O(logn),最坏情况下树呈现链状,为 O(n)O(n)。

第三种方法,高级版!也没完全整明白
方法三:Morris 遍历
思路与算法

有一种巧妙的方法可以在线性时间内,只占用常数空间来实现后序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其后序遍历规则总结如下:

  • 步骤1:新建临时节点,令该节点为 root;
  • 步骤2:如果当前节点的左子节点为空,则遍历当前节点的右子节点;
  • 步骤3:如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
  • 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
  • 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。倒序输出从当前节点的左子节点到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右子节点。
  • 步骤4:重复步骤 2 和步骤 3,直到遍历结束。
  • 这样我们利用 Morris 遍历的方法,后序遍历该二叉搜索树,即可实现线性时间与常数空间的遍历。
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        def addPath(node: TreeNode):
            tmp = list()
            while node:
                tmp.append(node.val)
                node = node.right
            res.extend(tmp[::-1])
        if not root:
            return list()
        res = list()
        p1 = root
        while p1:
            p2 = p1.left
            if p2:
                while p2.right and p2.right != p1:
                    p2 = p2.right
                if not p2.right:
                    p2.right = p1
                    p1 = p1.left
                    continue
                else:
                    p2.right = None
                    addPath(p1.left)
            p1 = p1.right
        addPath(root)
        return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值