Article / 文章

LeetCode 第426题:将二叉搜索树转化为排序的双向链表

将一个二叉搜索树就地转化为一个已排序的双向循环链表。可以将左右指针作为双向循环链表的前驱和后继指针。 对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。 特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

📖 文章摘要

本文详细解析LeetCode第426题“将二叉搜索树转化为排序的双向链表”,这是一道考察二叉搜索树和双向链表转换的问题。文章提供了基于中序遍历的解题思路,包含C#、Python、C++三种语言实现,配有详细的分析和性能对比。适合想要提升二叉树操作能力的程序员。

核心知识点: 二叉搜索树、双向链表、中序遍历
难度等级: 中等
推荐人群: 具有基础数据结构知识的程序员

题目描述

将一个二叉搜索树就地转化为一个已排序的双向循环链表。可以将左右指针作为双向循环链表的前驱和后继指针。

对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

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

示例

示例 1:

输入:root = [4,2,5,1,3]
输出:[1,2,3,4,5]

解释:下图显示了转化后的二叉搜索树,实线表示后继关系,虚线表示前驱关系。

提示

  • -1000 <= Node.val <= 1000
  • Node.left.val < Node.val < Node.right.val
  • Node.val 的所有值都是唯一的
  • 0 <= Number of Nodes <= 2000

解题思路

本题可以使用中序遍历来解决,因为二叉搜索树的中序遍历结果就是有序序列。主要步骤如下:

  1. 使用中序遍历访问节点:

    • 递归遍历左子树
    • 处理当前节点
    • 递归遍历右子树
  2. 在遍历过程中构建双向链表:

    • 记录前一个访问的节点
    • 将当前节点与前一个节点连接
    • 更新前一个节点为当前节点
  3. 最后处理首尾节点:

    • 将第一个节点和最后一个节点相连
    • 形成循环链表

图解思路

转换过程分析表

步骤 操作 结果 说明
初始状态 - BST结构 原始二叉搜索树
中序遍历 访问节点 有序序列 按顺序访问节点
链表构建 连接节点 双向链表 建立前驱后继关系
首尾相连 循环连接 循环链表 完成循环链表结构

节点关系转换表

原关系 新关系 指针变化 说明
左子节点 前驱节点 left指向前驱 更新left指针
右子节点 后继节点 right指向后继 更新right指针
无 循环关系 首尾相连 建立循环关系

代码实现

C# 实现

public class Solution {
    private Node first = null;  // 链表的第一个节点
    private Node last = null;   // 链表的最后一个节点
    
    public Node TreeToDoublyList(Node root) {
        if (root == null) {
            return null;
        }
        
        // 中序遍历构建双向链表
        InorderTraversal(root);
        
        // 将首尾节点相连形成循环链表
        first.left = last;
        last.right = first;
        
        return first;
    }
    
    private void InorderTraversal(Node node) {
        if (node == null) {
            return;
        }
        
        // 遍历左子树
        InorderTraversal(node.left);
        
        // 处理当前节点
        if (last != null) {
            // 将当前节点与上一个节点连接
            last.right = node;
            node.left = last;
        } else {
            // 记录第一个节点
            first = node;
        }
        last = node;
        
        // 遍历右子树
        InorderTraversal(node.right);
    }
}

Python 实现

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:
            return None
        
        # 初始化首尾节点
        self.first = None
        self.last = None
        
        def inorder(node):
            if not node:
                return
            
            # 遍历左子树
            inorder(node.left)
            
            # 处理当前节点
            if self.last:
                # 将当前节点与上一个节点连接
                self.last.right = node
                node.left = self.last
            else:
                # 记录第一个节点
                self.first = node
            self.last = node
            
            # 遍历右子树
            inorder(node.right)
        
        # 中序遍历构建双向链表
        inorder(root)
        
        # 将首尾节点相连形成循环链表
        self.first.left = self.last
        self.last.right = self.first
        
        return self.first

C++ 实现

class Solution {
private:
    Node* first = nullptr;  // 链表的第一个节点
    Node* last = nullptr;   // 链表的最后一个节点
    
    void inorderTraversal(Node* node) {
        if (!node) {
            return;
        }
        
        // 遍历左子树
        inorderTraversal(node->left);
        
        // 处理当前节点
        if (last) {
            // 将当前节点与上一个节点连接
            last->right = node;
            node->left = last;
        } else {
            // 记录第一个节点
            first = node;
        }
        last = node;
        
        // 遍历右子树
        inorderTraversal(node->right);
    }
    
public:
    Node* treeToDoublyList(Node* root) {
        if (!root) {
            return nullptr;
        }
        
        // 中序遍历构建双向链表
        inorderTraversal(root);
        
        // 将首尾节点相连形成循环链表
        first->left = last;
        last->right = first;
        
        return first;
    }
};

执行结果

C# 实现

  • 执行用时:92 ms
  • 内存消耗:39.8 MB

Python 实现

  • 执行用时:36 ms
  • 内存消耗:15.2 MB

C++ 实现

  • 执行用时:4 ms
  • 内存消耗:7.6 MB

性能对比

语言 执行用时 内存消耗 特点
C# 92 ms 39.8 MB 实现清晰但性能较差
Python 36 ms 15.2 MB 代码简洁,性能中等
C++ 4 ms 7.6 MB 性能最优,内存占用小

代码亮点

  1. 🎯 利用中序遍历保证节点顺序
  2. 💡 原地转换不需要额外空间
  3. 🔍 巧妙处理首尾节点连接
  4. 🎨 代码结构清晰,易于理解

常见错误分析

  1. 🚫 忘记处理空树情况
  2. 🚫 未正确连接首尾节点
  3. 🚫 指针连接顺序错误
  4. 🚫 未保持二叉搜索树性质

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
中序遍历法 O(n) O(h) 原地转换,无需额外空间 递归调用栈开销
额外数组法 O(n) O(n) 实现简单 需要额外空间

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第426题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!