本题来自左神《程序员代码面试指南》“找到二叉树中的最大搜索二叉子树”题目。

题目

给定一棵二叉树的头节点 head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这棵子树的头节点。

例如,二叉树如图 3-17 所示。
这棵树中的最大搜索二叉子树如图 3-18 所示。

要求:如果节点数为 N,则要求时间复杂度为 O(N),额外空间复杂度为 O(h),其中,h 为二叉树的高度。

题解

本题涉及二叉树面试题中一个很常见的套路,也是全书的一个重要内容。利用分析可能性求解在二叉树上做类似动态规划的问题。请读者理解并学习这种套路,本章还有很多面试题目是用这个套路求解的,我们把这个套路的名字叫作 树形 dp 套路

树形dp 套路使用前提:如果题目求解目标是S 规则,则求解流程可以定成以每一个节点为头节点的子树在S 规则下的每一个答案,并且最终答案一定在其中。

如何理解这个前提呢?以本题为例,题目求解目标是:整棵二叉树中的最大搜索二叉子树,这就是我们的规则。那么求解流程可不可以定成:在整棵二叉树中,求出每一个节点为头节点的子树的最大搜索二叉子树(对任何一棵子树都求出答案),并且最终答案(整棵二叉树的最大搜索二叉子树)一定在其中?当然可以。因此,本题可以使用套路。

树形dp 套路第一步:

以某个节点X 为头节点的子树中,分析答案有哪些可能性,并且这种分析是以X 的左子树、X 的右子树和X 整棵树的角度来考虑可能性的。

用本题举例。以节点X 为头节点的子树中,最大的搜索二叉子树只可能是以下三种情况中可能性最大的那种。

  • 第一种:X 为头节点的子树中,最大的搜索二叉子树就是X 的左子树中的最大搜索二叉子树。也就是说,答案可能来自左子树。比如,本例中,当X 为节点12 时。
  • 第二种:X 为头节点的子树中,最大的搜索二叉子树就是X 的右子树中的最大搜索二叉子树。也就是说,答案可能来自右子树。比如,本例中,当X 为节点6 时。
  • 第三种:如果X 左子树上的最大搜索二叉子树是X 左子树的全体,X 右子树上的最大搜索二叉子树是X 右子树的全体,并且X 的值大于X 左子树所有节点的最大值,但小于X 右子树所有节点的最小值,那么X 为头节点的子树中,最大的搜索二叉子树就是以X 为头节点的全体。
    也就是说,答案可能是用X 连起所有。比如,本例中,当X 为节点10 时。
树形dp 套路第二步:

根据第一步的可能性分析,列出所有需要的信息。

用本题举例,为了分析第一、二种可能性,需要分别知道左子树和右子树上的最大搜索二叉子树的头部,记为leftMaxBSTHead、rightMaxBSTHead,因为要比较大小,所以还需要分别知道左子树和右子树上的最大搜索二叉子树的大小,记为leftBSTSize、rightBSTSize,并且有了这些信息还能帮助分析第三种可能性,因为如果知道了leftMaxBSTHead,并且发现它正好是X 的左孩子节点,则说明X左子树上的最大搜索二叉子树是X 左子树的全体。同理,可以利用rightMaxBSTHead 来判断X右子树上的最大搜索二叉子树是否为X 右子树的全体。但是有这些还不够,因为第三种可能性还要求X 的值大于X 左子树所有节点的最大值,但小于X 右子树所有节点的最小值。因此,需要从左子树上取得左子树的最大值leftMax,从右子树上取得右子树的最小值rightMin。

汇总一下,为了分析所有的可能性,左树上需要的信息为:leftMaxBSTHead、leftBSTSize、leftMax;右树上需要的信息为:rightMaxBSTHead、rightBSTSize、rightMin。

树形dp 套路第三步:

合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构。

以本题举例,左树和右树都需要最大搜索二叉子树的头节点及其大小这两个信息,但是左树只需要最大值,右树只需要最小值,那么合并变成统一要求。信息结构请看如下的ReturnType 类。

树形 dp 套路第四步:

设计递归函数,递归函数是处理以X 为头节点的情况下的答案,包括设计递归的base case,默认直接得到左树和右树的所有信息,以及把可能性做整合,并且要返回第三步的信息结构这四个小步骤。本题的实现请看如下的process 方法。

package chapter_3_binarytreeproblem;public class Problem_07_BiggestSubBSTInTree {public static class Node {public int value;public Node left;public Node right;public Node(int data) {this.value = data;}}/*** 主方法:找到二叉树中的最大搜索二叉子树*/public static Node getMaxBST(Node head) {return process(head).maxBSTHead;}public static class ReturnType {public Node maxBSTHead; // 最大搜索二叉子树头结点public int maxBSTSize; // 最大搜索二叉子树大小public int min; // 右树只需最小值public int max; // 左树只需最大值public ReturnType(Node maxBSTHead, int maxBSTSize, int min, int max) {this.maxBSTHead = maxBSTHead;this.maxBSTSize = maxBSTSize;this.min = min;this.max = max;}}/*** 用递归函数设计一个二叉树后序遍历的过程:* 先遍历左子树收集信息,然后是右子树收集信息,最后在头结点做信息整合*/public static ReturnType process(Node X) {// base case : 如果子树是空树// 最小值为系统最大// 最大值为系统最小if (X == null) {return new ReturnType(null, 0, Integer.MAX_VALUE, Integer.MIN_VALUE);}// 默认直接得到左树全部信息ReturnType lData = process(X.left);// 默认直接得到右树全部信息ReturnType rData = process(X.right);// 【以下过程为信息整合】// 同时以X为头的子树也做同样的要求,也需要返回如ReturnType描述的全部信息// 以X为头的子树的最小值是:左树最小、右树最小、X的值,三者中最小的int min = Math.min(X.value, Math.min(lData.min, rData.min));// 以X为头的子树的最大值是:左树最大、右树最大、X的值,三者中最大的int max = Math.max(X.value, Math.max(lData.max, rData.max));// 如果只考虑可能性一和可能性二,以X为头的子树的“最大搜索二叉树大小”int maxBSTSize = Math.max(lData.maxBSTSize, rData.maxBSTSize);// 如果只考虑可能性一和可能性二,以X为头的子树的“最大搜索二叉树头节点”Node maxBSTHead = lData.maxBSTSize >= rData.maxBSTSize ? lData.maxBSTHead : rData.maxBSTHead;// 利用收集的信息,可以判断是否存在可能性三if (lData.maxBSTHead == X.left && rData.maxBSTHead == X.right && X.value > lData.max && X.value < rData.min) {maxBSTSize = lData.maxBSTSize + rData.maxBSTSize + 1;maxBSTHead = X;}// 【信息全部搞定】返回return new ReturnType(maxBSTHead, maxBSTSize, min, max);}// for test -- print treepublic static void printTree(Node head) {System.out.println("Binary Tree:");printInOrder(head, 0, "H", 17);System.out.println();}public static void printInOrder(Node head, int height, String to, int len) {if (head == null) {return;}printInOrder(head.right, height + 1, "v", len);String val = to + head.value + to;int lenM = val.length();int lenL = (len - lenM) / 2;int lenR = len - lenM - lenL;val = getSpace(lenL) + val + getSpace(lenR);System.out.println(getSpace(height * len) + val);printInOrder(head.left, height + 1, "^", len);}public static String getSpace(int num) {String space = " ";StringBuffer buf = new StringBuffer("");for (int i = 0; i < num; i++) {buf.append(space);}return buf.toString();}public static void main(String[] args) {Node head = new Node(6);head.left = new Node(1);head.left.left = new Node(0);head.left.right = new Node(3);head.right = new Node(12);head.right.left = new Node(10);head.right.left.left = new Node(4);head.right.left.left.left = new Node(2);head.right.left.left.right = new Node(5);head.right.left.right = new Node(14);head.right.left.right.left = new Node(11);head.right.left.right.right = new Node(15);head.right.right = new Node(13);head.right.right.left = new Node(20);head.right.right.right = new Node(16);printTree(head);Node bst = getMaxBST(head);printTree(bst);}}

树形 dp 套路就是以上四个步骤,就是利用递归函数设计一个二叉树后序遍历的过程:先遍历左子树收集信息,然后是右子树收集信息,最后在头节点做信息整合。因为是递归函数,所以对所有的子树要求一样,都返回ReturnType 的实例。依次求出每棵子树的答案,总答案一定在其中。既然是后序遍历,则时间复杂度为O(N)。

左神算法:找到二叉树中的最大搜索二叉子树(树形dp套路,Java版)相关推荐

  1. 找到二叉树中的最大搜索二叉子树

    找到二叉树中的最大搜索二叉子树 给定一棵二叉树的头节点head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这棵子树的头节点. 如果节点数为N,要求时间复杂度为 O(N),额外 ...

  2. 左神算法:最大值减去最小值小于或等于num的子数组的数量(Java版)

    本题来自左神<程序员面试代码指南>"最大值减去最小值小于或等于num的子数组的数量"题目. 题目 给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况: ...

  3. 左神算法:可见的山峰对数量(有重复值的情况)(Java版)

    本题来自左神<程序员面试代码指南>"可见的山峰对数量"题目. 题目 牛客在线OJ:可见的山峰对数量(进阶) 一个不含有负数的数组可以代表一圈环形山,每个位置的值代表山的 ...

  4. 找到二叉树中的最大搜索子树

    题目:  给定一棵二叉树的头节点head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉树,并返回这棵子树的头节点.(注意子树的概念) 基本思路:  以节点node为头的树中,最大的搜索二叉 ...

  5. 左神算法:二叉树的按层打印与ZigZag打印(Java版)

    本题来自左神<程序员代码面试指南>"二叉树的按层打印与ZigZag打印"题目. 题目 给定一棵二叉树的头节点 head,分别实现 按层 和 ZigZag 打印 二叉树的 ...

  6. 左神算法4.二叉树及相关习题理解

    题目1:实现二叉树的先序.中序.后序遍历[递归方式和非递归方式] 1.1递归实现 public class BinaryTreeWithRecur {public static class Node{ ...

  7. 左神算法:判断 t1 树中是否有与 t2 树拓扑结构完全相同的子树(Java版)

    本题来自左神<程序员代码面试指南>"判断 t1 树中是否有与 t2 树拓扑结构完全相同的子树"题目. 题目 给定彼此独立的两棵树头节点分别为 t1 和 t2,判断 t1 ...

  8. 左神算法:二叉树的最大 / 最小深度(普通+Morris遍历进阶)(Java版)

    二叉树的最小深度 题目 https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ 二叉树定义如下: // Definition fo ...

  9. 左神算法:判断二叉树是否为平衡二叉树(树形dp套路,Java版)

    本题来自左神<程序员代码面试指南>"判断二叉树是否为平衡二叉树"题目. 题目 平衡二叉树的性质为:要么是一棵空树,要么任何一个节点的左右子树高度差的绝对值不超过 1. ...

  10. 左神算法:判断 t1 树是否包含t2 树全部的拓扑结构(剑指 Offer 26. 树的子结构,Java版)

    本题来自左神<程序员代码面试指南>"判断 t1 树是否包含t2 树全部的拓扑结构"题目. 题目 剑指 Offer 26. 树的子结构 给定彼此独立的两棵树头节点分别为 ...

最新文章

  1. Aspose.Words导出图片 表格 Interop.Word
  2. 使用MOSS2007内置的更多FieldType
  3. Linux 进程基础
  4. python中with open写csv文件_Python中的CSV文件使用with语句的方式详解
  5. mysql 实现计数器_MySQL实现计数器的表设计及实现
  6. CrateDB 3.2.4 发布,大规模可伸缩的数据存储系统
  7. Python集合set与frozenset的区别
  8. 澳大利亚IT解决方案提供商使用OpManager节省了数万美元的IT维护成本
  9. smartsvn破解版
  10. 基于udp的服务器消息转发(群发)
  11. Mac怎么锁屏?苹果电脑怎么锁定屏幕
  12. 【Love2D】第0章-从零开始学习Love2D
  13. 【说透区块链系列】一文读懂什么是Web 3.0
  14. 12.14黄金白银实时行情分析,黄金原油解套操作策略
  15. 高数_第5章常微分方程_二阶线性微分方程解的结构
  16. 算法:递归(汉诺塔)
  17. mac可以正常联网,其他的app也可以正常使用,就是浏览器无法浏览网页,解决办法在这里
  18. 【操作系统】CPU(处理器)调度
  19. 0到9的数字替换成零 到 玖 的 大写汉字的函数
  20. 苹果7显示无法接通激活服务器,打电话时,显示对方手机暂时无法接通是什么原因?答案其实很简单...

热门文章

  1. 联想服务器如何u盘启动盘装系统,联想如何设置u盘启动
  2. 英语单词常用前缀(21-40)
  3. java中求矩形面积,java求矩形面积
  4. mysql怎么创建blog_「MySQL创建与删除数据库」- 海风纷飞Blog
  5. 关于王小云破解MD5
  6. 【信号用指数、正弦和余弦表示的原因】
  7. 实验1构建多连杆机器人模型
  8. 最新消息:原谷歌中国副院长刘骏任职人民搜索首席科学家
  9. [运动规划算法]Minimum Snap轨迹规划
  10. 支付宝统一收单接口实现支付宝支付