求二叉树中节点x的后继节点和前驱结点

求二叉树中节点x的后继节点和前驱结点

提示:理解二叉树中的后继节点和前驱节点


文章目录

  • 求二叉树中节点x的后继节点和前驱结点
    • @[TOC](文章目录)
  • 题目
  • 一、审题
  • 二、解题
    • 暴力中序遍历,将中序结果放入数组,返回x的索引-1(前驱),或者+1(后继)
    • 面试最优解:o(1)空间复杂度求后继节点
    • 面试最优解:o(1)空间复杂度求前驱节点
  • 总结

题目

求二叉树中节点x的后继节点和前驱结点
所谓后继节点:是二叉树中序遍历中x的后一个节点。
所谓前驱节点:是二叉树中序遍历中x的前一个节点。


一、审题

示例:比如:
求二叉树中节点x的后继节点和前驱结点_第1张图片
中序遍历:左头右
4 2 5 1 6 3 7
x=5,请问你后继节点是?1
前驱结点是?2

本题所用的节点,和树为:

public static class Node{
        public int value;
        public Node left;
        public Node right;
        public Node parent;
        public Node(int v){
            value = v;
        }
    }

    //构造一颗树,今后方便使用
    public static Node generateBinaryTree(){
        //树长啥样呢
        //          1
        //        2    3
        //      4  5  6  7
        //        8    9

        Node head = new Node(1);
        head.parent = null;
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        head.left = n2;
        head.right = n3;
        Node n4 = new Node(4);
        Node n5 = new Node(5);
        n2.left = n4;
        n2.right = n5;
        Node n6 = new Node(6);
        Node n7 = new Node(7);
        n3.left = n6;
        n3.right = n7;
        Node n8 = new Node(8);
        Node n9 = new Node(9);
        n5.left = n8;
        n6.right = n9;

        //树长啥样呢
        //          1
        //        2    3
        //      4  5  6  7
        //        8    9
        n2.parent = head;
        n3.parent = head;
        n4.parent = n2;
        n5.parent = n2;
        n6.parent = n3;
        n7.parent = n3;
        n8.parent = n5;
        n9.parent = n9;

        return n6;
    }

二、解题

注意: 除非这种题目特殊申明:节点有parent指针,其余情况下,遇到大厂的题目,一律认为没有parent指针!!!

咱们需要返回节点Node
不是value哦!

暴力中序遍历,将中序结果放入数组,返回x的索引-1(前驱),或者+1(后继)

你完全可以暴力中序遍历,耗费空间,然后把节点们放入数组
之后,找到x的索引i,返回i-1位置的节点为前驱节点,返回i+1位置的节点为后继节点
但是这既耗时,又耗费空间,不明智
求二叉树中节点x的后继节点和前驱结点_第2张图片

面试最优解:o(1)空间复杂度求后继节点

跳出中序遍历的数组

咱们直接看二叉树,能否直接从二叉树中定义,什么是后继节点
一切因为中序遍历时左,头,右的顺序

(1)当x节点没有右树时,往上找,一旦cur是其父节点parent的左子,则父节点parent就是x的后继节点。
求二叉树中节点x的后继节点和前驱结点_第3张图片
上图中,x=5,它是头结点的左树最右节点,它没右树,故往上找,
x的父节点parent=2,但是x是parent的右子!显然parent=2不是x的后继节点
继续让x去parent的位置找,cur=2,parent=1,此时发现,2节点的确是1节点的左子,故parent=1确实是当初x的后继节点

为什么?因为x是1节点的左树的最右节点 所以自然左树遍历完了,应该倒头节点了,然后才是右树——这既是中序遍历的顺序。
自然1节点应该是x的后继节点。

同样:
(2)如果节点x=1呢?它有右树,它的后继节点是谁呢?
有右树,中序遍历的下一个节点,也就是后继节点,一定是右树最左那个节点【毕竟中序遍历就是左头右】
你看是不是:
求二叉树中节点x的后继节点和前驱结点_第4张图片
其实任意位置节点x,也就这么两种情况
你要的是后继,那如果你在左树最后一个节点上,你就要去找左树的头做后继节点
如果你在头节点上,那你要去找这个头右树的最左那个节点,做后继节点。
就这么点事儿……

手撕代码:
——如何寻找一个树最左的节点?那就是不断地往左窜!!!直到cur左子是null,cur就是右树最左节点。

//给定节点head, 它有右树,找他的最左节点
    public static Node getMostLeftNode(Node head){
        Node cur = head;
        while (cur.left != null){
            cur = cur.left;//一直往左找
        }
        return cur;
    }

求二叉树中节点x的后继节点和前驱结点_第5张图片
——寻找后继节点手撕代码:

//复习后继节点,就两种情况:
    //(1)当x节点没有右树时,往上找,**一旦cur是其父节点parent的左子**,则父节点parent就是x的后继节点。
    //(2)如果节点x=1呢?它有右树,它的后继节点是谁呢?
    public static Node itsSuccessiveNode(Node x){
        if (x == null) return null;

        //(2)如果节点x=1呢?它有右树,它的后继节点是谁呢?
        if (x.right != null) return itsmostLeftNode(x.right);//右树最左节点
        //(1)当x节点没有右树时,往上找,**一旦cur是其父节点parent的左子**,则父节点parent就是x的后继节点。
        else {
            //往上找
            Node cur = x;
            Node parent = cur.parent;
            while (parent != null && parent.right == cur){
                //如果cur是parent的右子,不行,如果是左子,parent就是后继节点
                cur = parent;//继续往上窜
                parent = cur.parent;
            }
            //一旦parent是头结点了或者,是cur的左子,它就是后继节点
            return parent;
        }
    }
    public static void test3(){
        Node head = generateBinaryTree();
        Node suNode = itsSuccessiveNode(head);
        System.out.println(head.value +"的后继节点是 "+ suNode.value);
    }

    public static void main(String[] args) {
        test3();
    }

测试结果:

//树长啥样呢
        //          1
        //        2    3
        //      4  5  6  7
        //        8    9
8的后继节点是 5

面试最优解:o(1)空间复杂度求前驱节点

与前驱节点类似,咱摸排清楚啥是一个节点的前驱节点
(1)当节点x没有左树时,咱肯定是往上找,当节点cur是parent的左孩子时,parent肯定不是前驱节点,一旦cur是parent的右孩子,此时parent,就是当初x的前驱节点。你看看是不是:
下面x=6,前驱节点自然是1
cur=6,parent=3,parent的左子是cur,parent不是
cur=3,parent=1,parent的右子是cur,parent是6节点的前驱
求二叉树中节点x的后继节点和前驱结点_第6张图片
(2)当x有左树时,自然,前驱节点是左树上的最右节点,只需要不断往下窜找最右那个节点
比如x=1,左树的最右节点是5,自然5就是前驱
求二叉树中节点x的后继节点和前驱结点_第7张图片
这来源于中序遍历的原因,左头右,
x有左树,就看左树最右节点,
x没有左树,说明x是前驱节点的右树上的最左节点,咱就需要往上找,确定前驱
自己画图捋清楚这个关系,写代码就很简单了

——返回左树的最右节点:

//返回左树的最右节点:
    public static Node itsMostRightNode(Node x){
        Node cur = x;
        while (cur.right != null) cur = cur.right;
        //一旦cur右是空,窜不动了
        return cur;
    }

——寻找前驱节点手撕代码:

//寻找前驱节点:
    //(2)当x有左树时,自然,前驱节点是左树上的最右节点,只需要不断往下窜找最右那个节点
    //(1)当节点x没有左树时,咱肯定是往上找,当节点cur是parent的左孩子时,
    // parent肯定不是前驱节点,一旦cur是parent的右孩子,此时parent,就是当初x的前驱节点。
    public static Node itsPressiveNode(Node x){
        if (x == null) return null;

        //(2)当x有左树时,自然,前驱节点是左树上的最右节点,只需要不断往下窜找最右那个节点
        if (x.left != null) return itsMostRightNode(x.left);
        //(1)当节点x没有左树时,咱肯定是往上找,当节点cur是parent的左孩子时,
        // parent肯定不是前驱节点,一旦cur是parent的右孩子,此时parent,就是当初x的前驱节点。
        else {
            Node cur = x;
            Node parent = cur.parent;
            while (parent != null && parent.left == cur){
                //如果parent的左子是cur,parent不会是前驱,得往上找
                cur = parent;
                parent = cur.parent;
            }
            //一旦parent右子是cur,parent就是x的前驱节点
            return parent;
        }
    }
    public static void test4(){
        Node head = generateBinaryTree();
        Node suNode = itsPressiveNode(head);
        System.out.println(head.value +"的前驱节点是 "+ suNode.value);
    }

    public static void main(String[] args) {
//        test3();
        test4();
    }

看看结果:

//树长啥样呢
        //          1
        //        2    3
        //      4  5  6  7
        //        8    9
8的前驱节点是 2

总结

提示:重要经验:

1)画个图摸清楚,x节点的后继节点,x节点的前驱节点,它的本质是哪一个?这和左头右的中序遍历息息相关。
2)搞懂了核心思想,找前驱节点和后继节点并不难,分清楚x有没有左右树,这样就很快确定该怎么求。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

你可能感兴趣的:(大厂面试高频题之数据结构与算法,二叉树,后继节点,前驱节点,中序遍历,大厂笔试面试题)