剑指offer面试题36(java版):二叉树与双向链表

welcome to my blog

剑指offer面试题36(java版):二叉树与双向链表

题目描述

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

第四次做; 力扣上的这道题比牛客上复杂一点点, 需要将双向链表的首尾节点连接, 递归函数之后需要单独做一下处理; 递归函数逻辑: 获取已变成双向链表部分的头结点; 二叉树中的递归函数中会调用两次自身, 注意如何和根节点(当前条件)结合

class Solution {
    
    public Node treeToDoublyList(Node root) {
        //input check
        if(root==null)
            return root;
        //
        Node head = core(root);
        //找到双向链表的尾结点
        Node tail = head;
        while(tail.right!=null){
            tail = tail.right;
        }
        head.left = tail;
        tail.right = head;
        //
        return head;
    }
    
    //递归函数逻辑:获取已变成双向链表的部分的头结点
    private Node core(Node root){
        //base case
        if(root==null)
            return null;
        //root左子树构成的双向链表
        Node head1 = core(root.left);
        if(head1==null){
            root.left=null;
        }else{
            //找到链表的末尾节点
            Node cur = head1;
            while(cur.right!=null){
                cur = cur.right;
            }
            root.left=cur;
            cur.right=root;
        }
        //root右子树构成的双向链表
        Node head2 = core(root.right);
        if(head2==null){
            root.right=null;
        }else{
            root.right=head2;
            head2.left=root;
        }
        //返回链表的头结点
        return head1==null? root:head1;
    }
}

笔记

  • 使用递归函数处理某个节点和在递归函数内部处理该节点的左右子节点是不同的思考维度, 别弄混了
  • 题目要求不创建新节点, 注意理解这句话, 仍然能够创建TreeNode类型的指针

思路

  • 使用递归
  • 通用逻辑: 处理一个某一个节点; 将该节点与其左右子树连接; 返回合并后的双向链表的头结点

第三次做, 感觉稍微好一点点; 递归函数的逻辑:当前节点和整理成双向链表的左子树连接, 再将当前节点和整理成双向链表的右子树连接; 格外注意base case和返回值; base case:碰到null返回null, 由于是想让递归函数返回双向链表的头结点的, 但是碰到返回null怎么办? 如果返回值是null, 就在当前层返回node

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        //base case
        if(pRootOfTree==null)
            return null;
        //左子树改成双向链表后返回头结点
        TreeNode leftHead = Convert(pRootOfTree.left);
        TreeNode curr=leftHead;
        if(leftHead!=null){
            while(curr.right!=null)
                curr=curr.right;
            curr.right = pRootOfTree;
            pRootOfTree.left = curr;
        }
        TreeNode rightHead = Convert(pRootOfTree.right);
        pRootOfTree.right = rightHead;
        if(rightHead!=null)
            rightHead.left = pRootOfTree;
        return leftHead==null? pRootOfTree : leftHead;
    }
}

第二遍做,正确整合成一个递归函数, 不用使用O(N)的额外空间

  • 不要被中序遍历的“左根右”束缚, 开始时我觉得对于“根”的决策不能在“对右子树递归”后面,其实是可以的
/*
整体用一个递归函数实现时,逻辑错了,原因:没有想清楚递归函数要干什么,
其实最关键的错误是,一直从原始的搜索二叉树进行思考,殊不知搜索二叉树的结构已经被我改变了
但我还是以原始的二叉树作为参照进行思考

我用递归函数的目的:代码写在一块,整体性高
递归函数的处理逻辑:把当前节点和左子树改成的双向链表进行连接,把当前节点和右子树改成的双向链表进行连接,最后返回新链表的头结点
*/
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        //base case
        if(pRootOfTree==null)
            return null;
        //左子树
        TreeNode leftHead = Convert(pRootOfTree.left);
        TreeNode head;
        if(leftHead!=null){
            head=leftHead;
            //找到链表的末尾节点
            while(leftHead.right!=null)
                leftHead=leftHead.right;
            //左子树构成的双端链表和当前节点连接
            leftHead.right = pRootOfTree;
            pRootOfTree.left = leftHead;
        }
        else
            head=pRootOfTree;
        //右子树
        TreeNode rightHead = Convert(pRootOfTree.right);
        if(rightHead!=null){
            rightHead.left = pRootOfTree;
            pRootOfTree.right = rightHead;
        }
        else//这两句可以省略,因为指针指向默认为null
            pRootOfTree.right = null;//这两句可以省略,因为指针指向默认为null
        //返回链表的头结点
        return head;
    }
}

第二遍做,改写递归函数,遇到了逻辑错乱,递归掌握的还是不牢固, 下面是错误代码

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        //
        if(pRootOfTree==null)
            return null;
        //
        TreeNode head = null, temp = null;
        if(pRootOfTree.left!=null)
            head = Convert(pRootOfTree.left);
        
        //这下面是处理过程。调用递归函数不是处理过程
        //pRootOfTree.left指向其左子树的最大值
        if(pRootOfTree.left!=null){//是否存在左子树
            //找到左子树的最大值
            TreeNode curr = pRootOfTree.left;
            while(curr.right!=null)
                curr = curr.right;
            curr.right = pRootOfTree;
            pRootOfTree.left = curr;
        }
        //pRootOfTree.right指向其右子树的最小值
        if(pRootOfTree.right!=null){//是否存在右子树
            TreeNode curr = pRootOfTree.right;
            while(curr.left!=null)
                curr = curr.left;
            curr.left = pRootOfTree;
            pRootOfTree.right = curr;
            //head = temp.val <= curr.val ? temp : curr;
        }
        //找头结点
        if(pRootOfTree.left==null)
            head =  pRootOfTree;
        else{
            head = pRootOfTree.left;
            while(head.left!=null)
                head = head.left;
        }
        //这上面是处理过程。调用递归函数不是处理过程
        if(pRootOfTree.right != null)
            temp = Core(pRootOfTree.right);
        
        return head;
    }
}

第二次做,先用递归版中序遍历把节点放入队列中,再遍历队列处理的,这样会使用额外的O(N)空间。其实这两大步可以都在递归中处理,就不用再使用额外空间了

import java.util.LinkedList;

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null)
            return null;
        //
        LinkedList<TreeNode> ll = new LinkedList<>();
        Core(pRootOfTree, ll);
        TreeNode head, curr;
        //head=curr=ll.poll();//这句会执行几次ll.poll()?
        head = ll.poll();
        curr = head;
        while(!ll.isEmpty()){
            curr.right = ll.peek();
            ll.poll().left = curr;
            //update
            curr = curr.right;
        }
        return head;
    }
    public void Core(TreeNode node, LinkedList<TreeNode> ll){
        //base case
        if(node==null)
            return;
        //
        Core(node.left, ll);
        ll.add(node);
        Core(node.right, ll);
    }
}
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        // 要理解"不能创建任何新的结点"这个要求, 应该还是可以创建指针的
        //input check
        if(pRootOfTree==null)
            return null;
        //execute
        /*
        使用递归处理二叉搜索树的某一个节点时(思考中序遍历体现在哪里; 使用中序遍历遍历二叉搜索树, 结果是有序的)
        1.当前节点为curr, 考虑以curr为根的子树:
        2.curr的左子树(如果有的话)的最大值和curr相连接
        .. 处理根(这样就体现了中序遍历)
        3.curr的右子树(如果有的话)的最小值和curr相连接
        */
        TreeNode left = Convert(pRootOfTree.left);
        //找到左子树(如果存在的话)的最大值对应的节点
        while(left != null && left.right!=null){
            left = left.right;
        }
        if(left!=null){
            left.right = pRootOfTree;
            pRootOfTree.left = left;
        }
        
        // 找到右子树(如果存在的话)的最小值对应的节点
        TreeNode right = Convert(pRootOfTree.right);
        if(right != null){
            pRootOfTree.right = right;
            right.left = pRootOfTree;
        }
        // 最后返回双向链表的头结点
        TreeNode curr = pRootOfTree;
        while(curr.left != null)
            curr = curr.left;
        return curr;
    }
}
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/

你可能感兴趣的:(剑指offer,剑指offer)