提示:本节仍然是重点说二叉树的DP递归套路,非常重要而且容易理解
二叉树的动态规划树形DP递归套路系列文章有这些,可以帮助你快速掌握树形DP的题目解题思想,就一个套路:
(1)判断二叉树是否为平衡二叉树?树形DP,树形动态规划的递归套路
(2)求二叉树中,任意两个节点之间的距离最大值是多少
(3)求二叉树中,包含的最大二叉搜索子树的节点数量是多少
(4)求二叉树中,包含的最大二叉搜索子树的头节点是谁,它包含的节点数量是多少
(5)求公司派对的最大快乐值
(6)判断二叉树是否为满二叉树
(7)判断二叉树是否为完全二叉树
给你一个二叉树head,请你寻找节点x和节点y的最低公共祖先
所谓最低公共祖先:
就是x和y往上寻找父节点的过程中,第一次相遇的那个节点
换句话说,就是一个节点cur,它是第一个节点,以自己开头的树上,同时已经有x和y节点。
示例:下列x和y的最低公共祖先是:cur
下列x和y节点的最低公共祖先是A
本题用的二叉树:
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
//构造一颗树,今后方便使用
public static ArrayList<Node> generateBinaryTree() {
//树长啥样呢
// 1
// 2 3
// 4 5 6 7
// 8
// 9
Node head = new Node(1);
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);
n4.left = n8;
Node n9 = new Node(9);
n8.left = n9;
ArrayList<Node> res = new ArrayList<>();
res.add(head);
res.add(n2);
res.add(n9);
return res;
}
95%的二叉树动态规划问题,都可以用以下套路解决:
一、定义个信息类:Info
收集x左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。
二、树形DP递归套路: 来到x节点
1)base case:考虑叶节点应该返回的信息Info
2)先收集左树和右树的信息Info
3)综合2)整理x自己的信息,并返回;
三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。
来,咱们举例说明:实践知真理!
要深刻理解最低公共祖先的含义:它是第一个节点,以自己开头的树上,同时已经有x和y节点
95%的二叉树动态规划问题,都可以用以下套路解决:
一、定义个信息类:Info
收集h左树和右树都有的信息(左右树都有哦,而不是针对某单独的左树或者右树),比如是否平衡?树的高度,总之就是有的信息,不管你是String还是Boolean还是Integer类型的信息。经常是要讨论与x有关,还是 与x无关。
如果我们去x开头的树,下面要啥信息呢?
(1)h这个树,包含的x和y的最低公共祖先是谁?ancestor,这本就是本题要求的
(2)咋找才能说明ancestor找到了呢,至少,在你这个节点x这里,我们同时已经找到了x节点,也找到了y节点
比如,A之所以是x和y的最低公共祖先,就是因为A是第一个点,满足,在A这个子树上,同时已经找到了x节点,也同时找到了y节点。
故,咱们还需要在树上保留这俩信息:hasFindX,hasFindY
信息类可以这么定义:
public static class Info{
public Node ancestor;//找到祖先了吗
public boolean xisFind;
public boolean yisFind;
public Info(Node a, boolean x, boolean y){
ancestor = a;
xisFind = x;
yisFind = y;
}
}
二、树形DP递归套路: 来到x节点
定义函数f(Node h),用来收集x这个树上的信息:
1)base case:考虑叶节点应该返回的信息Info
遇到叶节点的左右子为null
这个时候,祖先肯定是null,没找到呢,null不管用
找到了x吗?hasFindX=false;
找到了y吗?hasFindX=false;
if (K == null) return new Info(null, false, false);//没xy,没有祖先
2)先收集左树和右树的信息Info
好说:
//左边收信息,右边收信息
Info leftInfo = process(K.left, x, y);
Info rightInfo = process(K.right, x, y);
3)综合2)整理x自己的信息,并返回;
——整理找到了x吗?
如果h就是x或者,h的左边找到了x,或者h的右边找到了x,算是x找到了
——整理找到了y吗?
如果h就是y,或者h的左边找到了y,或者h的右边找到了y,算是x找到了
——整理ancestor是谁呢?
注意,如果h左边已经找到了ancestor,那就是它,用左边的替代,当初那个点就是最低公共祖先,你现在h是也没用,因为h不是最低的。
当然如果右边先找到了,右边的那个最低公共祖先,保持不变
啥时候h就是最低公共祖先呢?
第一保证ancestor到目前位置,还是null没找到,且
第二,左边找到了x,右边找到了y,或者左边找到了y右边找到了x【不可能同时找到,否则最低公共祖先上面就有了】,又或者,左边找到了x,h=y,又或者,右边找到了x,h=y,又或者左边找到了y,h=x,又或者右边找到了y,h=x
扯这么多没用……
第二点一句话概括,即同时找到了x,又找到了y(xisFind && yisFind)
如果上面第一第二都满足,则h当前就是ancestor,更新,从今往后就是这一个结果,伴随到最初的定点。
这么一分析,真的非常强大,这个树形DP的解题思路,可以轻松地解决这个题
代码很简单:
//复习:收集x树的信息
public static Info f(Node head, Node x, Node y){
//去找head这个树上x与y的最低公共祖先是谁?
if (head == null) return new Info(null, false, false);
//左右收集信息
Info left = f(head.left, x, y);
Info right = f(head.right, x, y);
//整理信息y
boolean xIsFind = head == x || left.xisFind || right.xisFind;
boolean yIsFind = head == y || left.xisFind || right.xisFind;
Node ancestor = null;
if (left.ancestor != null) ancestor = left.ancestor;
if (right.ancestor != null) ancestor = right.ancestor;//左右已经有了,那算了
//仍然没有找到,则看看head是不是可能成为ancestor
if (ancestor == null && xIsFind && yIsFind) ancestor = head;
//只要此刻x与y同时已经找到了,不管在哪,head就是ancestor
return new Info(ancestor, xIsFind, yIsFind);
}
三、从题目给的二叉树head开始求(2),得到了一个head的信息Info,找里面咱们要用的那个信息:比如是否平衡?返回它。
调用嘛好说 ,就找head的信息,返回里面的ancestor
//调用很简单
public static Node ancestorOfXY(Node head, Node x, Node y){
if (head == null) return null;
Info info = f(head, x, y);
return info.ancestor;
}
//树长啥样呢
// 1
// 2 3
// 4 5 6 7
// 8
// 9
//x = 9, y=5
public static void test2(){
ArrayList<Node> arrayList = generateBinaryTree();//生成我们要找到节点
Node cur = findLowestPublicNode2(arrayList.get(0),
arrayList.get(1),
arrayList.get(2));
System.out.println(cur.value);
}
public static void main(String[] args){
test2();
}
结果:
2
很容易吧,不管面试还是笔试,看到这个题,那就很简单的。
最直白的做法,就是去x和y同时往上找,看看寻找路径第一次碰面在哪个节点?
这就需要寻找父节点了,可是
咱得二叉树,没有parent指针,那咋知道x的父节点呢?
用哈希表存,然后遍历每一个二叉树的点x,把x左子右子左key,x左value,放在哈希表中
//遍历二叉树,填表,各个节点的父节点
public static void fillParentMapReview(Node head, HashMap<Node, Node> map){
if (head.left == null && head.right == null) return;//不管叶节点
if (head.left != null){//左边有左子,就可以给左子上表
map.put(head.left, head);
fillParentMapReview(head.left, map);//DFS遍历玩
}
if (head.right != null){//有右子,给右子上表,然后DFS遍历玩
map.put(head.right, head);
fillParentMapReview(head.right, map);//先天他们,再来填我--随意
}
}
然后,咱们开始寻找
有了x和y地址,那,先把x的父节点,一条线,沿途父节点全部加入集合set中
从y开始找,沿途父节点,去set对比,第一个set中出现的与x共父节点的那个点,就是最低公共祖先
这个思想很简单,无非就是耗费时间和额外空间。
手撕代码:
//复习,用哈希表存父节点,x父节点沿途加入set,然后沿途找y的父节点,看看谁先出现在set中
public static Node hashMapFindAncestorOfXY(Node head, Node x, Node y){
if (head == null) return null;
if (x == y) return x;//本来就相同,何必呢
HashMap<Node, Node> map = new HashMap<>();
//遍历二叉树,填表
fillParentMapReview(head, map);
//有了父节点表,那就可以先把x的父节点路径搞到set中
HashSet<Node> set = new HashSet<>();
set.add(x);//x也在里面
Node cur = map.get(x);//拿到x的父节点
while (cur != null){
//有父节点,加进来
set.add(cur);
cur = map.get(cur);//继续上窜
}
//沿途找y的父节点,看看第一个在set中的那个点,就是最低公共祖先
cur = map.get(y);
while (!set.contains(cur)){
//沿途父节点,一直不在set中,继续上窜
cur = map.get(cur);
}
//一旦发现set有了cur了,cur即最低公共祖先
return cur;
}
测试一把:
//树长啥样呢
// 1
// 2 3
// 4 5 6 7
// 8
// 9
//x = 9, y=5
public static void test2(){
ArrayList<Node> arrayList = generateBinaryTree();//生成我们要找到节点
Node cur = findLowestPublicNode2(arrayList.get(0),
arrayList.get(1),
arrayList.get(2));
System.out.println(cur.value);
cur = ancestorOfXY(arrayList.get(0),
arrayList.get(1),
arrayList.get(2));
System.out.println(cur.value);//复习:树形DP实现
cur = hashMapFindAncestorOfXY(arrayList.get(0),
arrayList.get(1),
arrayList.get(2));
System.out.println(cur.value);//复习哈希表实现的
}
public static void main(String[] args){
test2();
}
结果OK
2
2
2
提示:重要经验:
1)树形DP的递归套路,说了多少次,非常非常重要,而且形式简单,这是破解95%二叉树的动态规划题目的,一定要熟悉
2)此题,理解最低公共祖先的关键,在于第一次在某个点为头的树中,同时找到了x和y节点,这使得整理信息,变成了一句话。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。