LeetCode 99 搞懂二叉树的中序遍历

原题:链接

二叉搜索树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。

示例1:
在这里插入图片描述
示例2:
在这里插入图片描述
进阶:
你能想出一个只使用常数空间的解决方案吗?

这是一道难度为hard的题,可以通过这道题熟悉递归并了解中序遍历的几种方法。

暂时先不考虑进阶的要求,我们先捋一下思路。

首先这是一个二叉搜索树,因为意外有两个点被交换了,所以我们需要尽可能保持树的结构去找到那两个点。其实我们并没有必要真正找到那两个点在内存中的地址,由题目要求可知,我们只需找到错误交换的那两个点,然后交换它们的value值即可。怎么找呢?我们需要遍历一遍这二叉树,然后通过遍历得到的顺序,找到我们需要的两个点,按顺序分别记为first、second。

可以证实,对于二叉搜索树而言,其中序遍历的结果为一个升序的序列。因此就可以通过观察当前数中序序列,找出被交换的两个点。

例如:

2
/ \
3 1

很明显其中序遍历结果为[3, 2, 1]。从升序的角度我们就可以确定first节点就是值为3的那个节点,second节点就是值为1的那个节点。

一般地,如果遍历中当前节点cur.val < 上一个节点pre.val,我们的first节点就是这个pre节点。而在first节点确定后,我们的second节点就是cur节点。用代码描述如下:

if not first and cur.val < pre.val:
    first = pre
if first and cur.val < pre.val:
    second = cur

这个问题解决了,剩下的就是将上面的逻辑整合到二叉搜索树的中序遍历中。

这里列举学习一下中序遍历的三种方法,递归版本、迭代版本和Morris版本。

很明显的就是递归版本和迭代版本,但会有O(h)(h为树的高度)空间复杂度,而进阶的O(1)的空间复杂度要求则需要使用Morris中序遍历了。

解法一

递归版本

def recoverTree(root):
    # 初始化
    # 递归过程的全局变量通过类属性保存
    self.first = None
    self.second = None
    self.pre = TreeNode(float('-inf'))
    def inorder_recursion(root):
        # 中序模板
        if not root:
            return
        inorder_recursion(root.left)
        # root为遍历到的当前节点
        # 比较操作
        if not self.first and root.val < self.pre.val:
            self.first = self.pre
        if self.first and root.val < self.pre.val:
            self.second = root
        # 记录上一个节点 
        # 用于递归返回时比较
        self.pre = root
        inorder_recursion(root.right)
    # 中序遍历并取得first和second
    inorder_recursion(root)
    # 交换即可
    if self.first and self.second:
        self.first.val, self.second.val = self.second.val, self.first.val

解法二

迭代版本

def recoverTree(root):
    # 初始化
    first = None
    second = None
    pre = TreeNode(float('-inf'))
    
    stack = []
    cur = root
    while cur or stack:
        # 迭代版本模板
        while cur:
            stack.append(cur)
            cur = cur.left
        # 获取当前节点
        cur = stack.pop()
        # 比较操作
        if not first and cur.val < pre.val:
            first = pre
        if first and cur.val < pre.val:
            second = cur
        # 记录上一个节点
        pre = cur
        # 考查右儿子
        cur = cur.right
    # 交换即可
    if first and second:
        first.val, second.val = second.val, first.val

解法三

Morris版本

我们先理清一下Morris中序遍历的过程:

  1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
  2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点(此处要避免与当前节点相等造成死循环):
    a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
    b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。

重复以上直到当前节点为空。
自己刚开始看的时候也是很头大,但不要害怕。其实画画图,一步一步尝试就能大概理清了。

Morris中序遍历通过寻找前驱节点,达到一种类似于消除左子树并右展开为链表的感觉。感兴趣可以参考这道LeetCode题:Click me

def recoverTree(root):
    # 初始化
    first = None
    second = None
    preNode = TreeNode(float('-inf'))
    
    cur = root
    while cur:
        # Morris遍历的模板
        if cur.left:
            # 如果左儿子不为空
            pre = cur.left
            # 找到左子树最右的节点
            while pre.right and pre.right != cur:
                pre = pre.right
            # 如果此节点无右儿子
            if not pre.right:
                pre.right = cur
                cur = cur.left
                continue
            # 如果有右儿子
            # 记得置空右儿子
            # 恢复树的结构
            pre.right = None
            
        # 比较操作
        # cur为当前节点
        if not first and cur.val < preNode.val:
            first = preNode
        if first and cur.val < preNode.val:
            second = cur
        # 记录上一个节点
        preNode = cur
        cur = cur.right
     # 交换即可
    if first and second:
        first.val, second.val = second.val, first.val

其实中序遍历有很多实际的应用,我暂且能想到的就是以前看过利用二叉搜索树中序求解并写出一个四则运算表达式(24 Point Game)。后续如有更新或者补充会加上,记录自己的学习历程。over!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Key Board

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值