leetcode 链接:剑指 Offer 36. 二叉搜索树与双向链表

题目

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

为了让您更好地理解问题,以下面的二叉搜索树为例:
[中等] 剑指 Offer 36. 二叉搜索树与双向链表 - 图1
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
[中等] 剑指 Offer 36. 二叉搜索树与双向链表 - 图2
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

解答 & 代码

写法一:创建虚拟头节点

  • 初始化 pre 节点为 NULL,代表中序遍历的前一个节点
  • 递归中序遍历将二叉搜索树转换为双向链表
    • 递归结束条件:当前节点为空,直接返回
    • 递归遍历左子树
    • 处理当前节点(root
      • 如果 pre 为空,代表当前节点是最小元素节点,即转换后双向链表的 head 节点,另 head = root
      • 如果 pre 不为空,将 preright 指针指向当前节点
      • 将当前节点的 left 指针指向 pre
      • 更新 pre = root
    • 递归遍历右子树
  • 将双向链表的头尾节点也双向链接

    1. /*
    2. // Definition for a Node.
    3. class Node {
    4. public:
    5. int val;
    6. Node* left;
    7. Node* right;
    8. Node() {}
    9. Node(int _val) {
    10. val = _val;
    11. left = NULL;
    12. right = NULL;
    13. }
    14. Node(int _val, Node* _left, Node* _right) {
    15. val = _val;
    16. left = _left;
    17. right = _right;
    18. }
    19. };
    20. */
    21. class Solution {
    22. private:
    23. Node* pre; // 中序遍历的前一个节点
    24. // 递归中序遍历将二叉搜索树转换为双向链表
    25. void inOrder(Node* root)
    26. {
    27. if(root == NULL) // 递归结束条件
    28. return;
    29. inOrder(root->left); // 递归中序遍历左子树
    30. // 中序位置:将当前节点和中序遍历的前一个节点连起来
    31. pre->right = root;
    32. root->left = pre;
    33. pre = root; // 更新 pre
    34. inOrder(root->right); // 递归中序遍历右子树
    35. }
    36. public:
    37. Node* treeToDoublyList(Node* root) {
    38. if(root == NULL)
    39. return NULL;
    40. Node* head = new Node(0); // 虚拟头节点
    41. pre = head; // 将中序遍历的前一个节点初始化指向虚拟头节点
    42. // 递归中序遍历将二叉搜索树转换为双向链表
    43. inOrder(root);
    44. // 将头尾节点连接
    45. pre->right = head->right;
    46. head->right->left = pre;
    47. return head->right;
    48. }
    49. };

    复杂度分析:设二叉树节点数为 N

  • 时间复杂度 O(N)

  • 空间复杂度 O(log N)

执行结果:

  1. 执行结果:通过
  2. 执行用时:4 ms, 在所有 C++ 提交中击败了 93.23% 的用户
  3. 内存消耗:7.3 MB, 在所有 C++ 提交中击败了 88.22% 的用户

写法二:不创建任何新节点

题目要求不能创建任何新的节点,而写法一实际上还是新建了虚拟头节点。
下面的代码没有创建任何新节点:

  • 通过 head 是否为 nullptr 来判断当前节点是否是转换后的头节点

    /*
    // Definition for a Node.
    class Node {
    public:
      int val;
      Node* left;
      Node* right;
    
      Node() {}
    
      Node(int _val) {
          val = _val;
          left = NULL;
          right = NULL;
      }
    
      Node(int _val, Node* _left, Node* _right) {
          val = _val;
          left = _left;
          right = _right;
      }
    };
    */
    class Solution {
    private:
      Node* head;        // 转换后链表的头节点
      Node* pre;        // 当前遍历到的前一个节点
      // 递归中序遍历,将二叉搜索树转化为双向链表
      void inOrder(Node* root)
      {
          // 1. 递归结束条件:如果当前为空节点,则直接返回
          if(root == NULL)
              return;
    
          // 2. 递归遍历左子树
          inOrder(root->left);
    
          // 3. 中序位置
          // 如果头节点为空,说明当前节点是转换后链表的头节点
          if(head == nullptr)
              head = root;
          // 否则,将 pre 节点和当前节点连接
          else
          {
              pre->right = root;
              root->left = pre;
          }
          pre = root;        // 更新 pre 节点
    
          // 4. 递归遍历右子树
          inOrder(root->right);
      }
    public:
      Node* treeToDoublyList(Node* root) {
          if(root == NULL)
              return NULL;
    
          head == nullptr;        // 初始将 head 设为空
          pre = nullptr;
          inOrder(root);
          // 将双向链表头尾连接起来
          pre->right = head;        // 链表尾节点指向头节点
          head->left = pre;        // 链表头节点指向尾节点
          return head;
      }
    };
    

    复杂度分析:设二叉树节点数为 N

  • 时间复杂度 O(N)

  • 空间复杂度 O(log N)

执行结果:

执行结果:通过

执行用时:4 ms, 在所有 C++ 提交中击败了 93.42% 的用户
内存消耗:7.3 MB, 在所有 C++ 提交中击败了 83.15% 的用户