• 剑指offer_36_二叉搜索树与双向链表


    二叉搜索树与双向链表

    题目链接https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/

    题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

    为了让您更好地理解问题,以下面的二叉搜索树为例:

    img

    我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

    下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

    img

    特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

    题目解析

    题目解析内容来自于题解中Krahets

    题目的意思还是很明确的。基于二叉搜索树的性质,我们使用中序遍历可得到二叉搜索树的递增序列

    将 二叉搜索树 转换成一个 排序的循环双线链表 ,其中包含三个要素:

    1. 排序链表:节点应该从小到大排序,因此应使用 中序遍历 从小到大 访问数节点;
    2. 双向链表:在构建相邻节点(设前驱节点 pre,当前节点 cur )关系时,不仅应 pre.right = cur, cur.left = pre。
    3. 循环链表:设链表头结点 head 和尾结点 tail , 则应构建 head.left = tail 和 tail.right = head。

    Picture14.png

    中序遍历 为 “左 根 右” 顺序,递归实现代码如下:

    # 中序遍历
    def in_traversal(root):
        ret = []
        def traversal(root):
            if not root:
                return
            traversal(root.left)
            ret.append(root.val)
            traversal(root.right)
        traversal(root)
        return ret
    

    根据以上分析,考虑使用中序遍历访问树的各节点 cur ;并在访问每个节点时构建 cur 和前驱节点 pre 的引用指向;中序遍历完成后,最后构建头节点和尾节点的引用指向即可。

    算法流程

    in_traversal(cur): 递归法中序遍历

    1. 终止条件:当节点 cur 为空时,代表已经越过叶子节点,此时可直接返回

    2. 递归左子树,即 in_traversal(cur.left)

    3. 构建链表

      1. pre 为空时:代表当前访问的是链表的头结点,记为 head
        1. pre 不为空时:修改双向节点引用,即 pre.right = cur, cur.left = pre;
        2. 保存 cur:更新 pre = cur, 即节点 cur 是后继节点的 pre
    4. 递归右子树,即 in_traversal(cur.right)

    treeToDoublyList(root):

    1. 特例处理:若节点 root 为空,则直接返回;
    2. 初始化:空节点 pre
    3. 转化为双向链表:调用 in_traversal(root);
    4. 构建循环链表:中序遍历完成后,head 指向头节点,pre指向尾结点,因此修改 headpre 的双向节点引用即可
    5. 返回值:返回链表的头节点 head 即可

    复杂度分析

    • 时间复杂度:O(N)
    • 空间复杂度:O(N)

    代码

    class Solution:
        def treeToDoublyList(self, root: 'Node') -> 'Node':
            def in_traversal(cur):
                if not cur:
                    return 
                in_traversal(cur.left)  # 递归左子树
                if self.pre:  # 修改节点引用
                    self.pre.right, cur.left = cur, self.pre
                else:  # 记录头节点
                    self.head = cur
                self.pre = cur  # 保存 cur  当前节点变成 pre
                in_traversal(cur.right)  # 递归右子树
                
            if not root:
                return
            self.pre = None
            in_traversal(root)
            self.head.left, self.pre.right = self.pre, self.head
            return self.head
    

    附一个迭代版方法:

    class Solution:
        def treeToDoublyList(self, root: 'Node') -> 'Node':
            if not root:
                return
            stack = []
            pre = None
            while stack or root:
                if root:
                    stack.append(root)
                    root = root.left
                else:
                    node = stack.pop()
                    if not pre:
                        head = node
                    else:
                        pre.right = node
                    node.left = pre
                    pre = node
                    root = node.right
            head.left, node.right = node, head
            return head
    
  • 相关阅读:
    c# 设计模式 之:装饰模式
    c# 设计模式 之:抽象工厂
    c# 设计模式 之:简单工厂、工厂方法、抽象工厂之小结、区别
    c# 设计模式 之:工厂模式之---工厂模式
    c# 设计模式 之:工厂模式之---简单工厂
    uml
    ASP.NET应用程序生命周期
    C语言可变参数个数
    软件开发过程中的视角
    UML类图与类的关系详解
  • 原文地址:https://www.cnblogs.com/yezigege/p/13306945.html
Copyright © 2020-2023  润新知