求二叉树中节点x的后继节点和前驱结点
提示:理解二叉树中的后继节点和前驱节点
题目
求二叉树中节点x的后继节点和前驱结点
所谓后继节点:是二叉树中序遍历中x的后一个节点。
所谓前驱节点:是二叉树中序遍历中x的前一个节点。
一、审题
示例:比如:
中序遍历:左头右
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位置的节点为后继节点
但是这既耗时,又耗费空间,不明智
面试最优解:o(1)空间复杂度求后继节点
跳出中序遍历的数组
咱们直接看二叉树,能否直接从二叉树中定义,什么是后继节点?
一切因为中序遍历时左,头,右的顺序
(1)当x节点没有右树时,往上找,一旦cur是其父节点parent的左子,则父节点parent就是x的后继节点。
上图中,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,也就这么两种情况
你要的是后继,那如果你在左树最后一个节点上,你就要去找左树的头做后继节点
如果你在头节点上,那你要去找这个头右树的最左那个节点,做后继节点。
就这么点事儿……
手撕代码:
——如何寻找一个树最左的节点?那就是不断地往左窜!!!直到cur左子是null,cur就是右树最左节点。
//给定节点head, 它有右树,找他的最左节点
public static Node getMostLeftNode(Node head){
Node cur = head;
while (cur.left != null){
cur = cur.left;//一直往左找
}
return cur;
}
——寻找后继节点手撕代码:
//复习后继节点,就两种情况:
//(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节点的前驱
(2)当x有左树时,自然,前驱节点是左树上的最右节点,只需要不断往下窜找最右那个节点
比如x=1,左树的最右节点是5,自然5就是前驱
这来源于中序遍历的原因,左头右,
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,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。