每日一题 - 剑指 Offer 36. 二叉搜索树与双向链表

题目信息

  • 时间: 2019-06-29

  • 题目链接:Leetcode

  • tag: 二叉搜索树 中序遍历 递归 深度优先搜索

  • 难易程度:中等

  • 题目描述:

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

示例:

              4
             / \
            2   5
           / \   
          1  3  

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

提示

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

解题思路

本题难点

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

  • 排序链表: 节点应从小到大排序,因此应使用 中序遍历 “从小到大”访问树的节点;

  • 双向链表: 在构建相邻节点(设前驱节点 pre ,当前节点 cur )关系时,不仅应 pre.right=cur ,也应 cur.left=pre 。

  • 循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left=tail 和 tail.right=head 。

具体思路

二叉搜索树的中序遍历为 递增序列

  • 中序遍历

    void dfs(TreeNode root) {
        if(root == null) return;
        dfs(root.left); // 左
        System.out.println(root.val); // 根
        dfs(root.right); // 右
    }
    

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

代码

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    //pre本来是前序节点,随着中序遍历,最终会成为中序遍历的尾节点
    //head指向中序遍历的头节点
    Node pre,head;
    public Node treeToDoublyList(Node root) {
        if(root == null){
            return null;
        }
        //深度优先搜索二叉搜索树
        dfs(root);
        //将头节点head的左指针指向尾节点pre
        head.left = pre;
        //将尾节点pre的左指针指向头节点head
        pre.right = head;
        //返回结果
        return head;

    }

    void dfs(Node cur){
      //终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
        if(cur == null){
            return;
        }
        //递归遍历左子树
        dfs(cur.left);
        //当前节点cur不存在左子树时,此时cur无前驱节点,即前驱节点pre==null,cur为中序遍历的第一个节点
        if(pre == null){
            //将中序遍历的头节点cur赋予双向链表的头节点head
            head = cur;
        }else{
            //当前节点cur存在前置节点pre,将pre的右指针指向cur
            pre.right = cur;
            //当前节点cur存在前驱节点pre,将cur的左指针指向pre,形成双向链表
        		cur.left = pre;
        }
        //将前驱节点pre后移,保存当前节点cur
        pre = cur;
        //递归遍历右子树
        dfs(cur.right); 
    }
}

复杂度分析:

  • 时间复杂度 O(N) : N 为二叉树的节点数,中序遍历需要访问所有节点。
  • 空间复杂度 O(N) : 最差情况下,即树退化为链表时,递归深度达到 N,系统使用 O(N) 栈空间。

其他优秀解答

解题思路

中序遍历的非递归,使用栈的先进后出特性。

代码

public Node treeToDoublyList(Node root) {
        if(root == null){
            return null;
        }
  			//用栈实现
         Stack<Node> stack = new Stack<>();
         Node current = root;
         Node pre = null, head = null;
         while(!stack.isEmpty() || current != null) {
           //内层循环将当前数据入栈
             while(current != null) {
                 stack.push(current);
                 current = current.left;
             }
           //出栈并将该元素放入到链表中
             current = stack.pop();
             if(pre == null) {//处理头结点
                 head = current;
             }else {
                 pre.right = current;
                 current.left = pre;
             }
            pre = current;
           ////将cur指向栈顶元素的右孩子
            current = current.right;
         }
         pre.right = head;
         head.left = pre;
         return head;
    }
posted @ 2020-07-01 12:54  小锵同学、  阅读(115)  评论(0编辑  收藏  举报