二叉树的递归套路——最大二叉搜索子树大小

210 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

【题目3】

给定一颗二叉树的头结点head,返回这颗二叉树中最大的二叉搜索子树的大小

搜索二叉树定义:整棵树上没有重复值,左树比头结点小,右树比头结点大,每颗子树都如此。比如如下这棵树就是搜索二叉树 在这里插入图片描述 但是在一棵二叉树中,未必全部都是搜索二叉树,比如如下这棵树, 在这里插入图片描述 上图这棵树中的最大二叉搜索子树是以4为头结点的子树,而不是以3为头结点的子树。

此题还可以改为求一颗二叉树中最大二叉搜索子树的结点数量。

所以接下来列可能性(二叉树的递归套路最关键的地方就是列可能性):

第一种可能性:与X结点无关:要找的最大二叉搜索子树不以X结点为头结点,所以需要的信息就是,1)左树上满足条件的最大二叉搜索子树的大小 || 2)右树上满足条件的最大二叉树搜索子树的大小(大小结束结点个数)

第二种可能性:与X结点有关:要找的最大二叉搜索子树以X结点为头结点, 所以需要的信息就是,1)左树整体是搜索二叉树 && 2)右树整体是搜索二叉树 && 3)左树上的最大值<X && 4)右树上的最小值>X。

那么左树和右树最终需要的信息是哪些?

左树:1)左树上最大二叉搜索子树的大小,2)左树整体是否为搜索二叉树,3)左树上的最大值 右树:1)右树上最大二叉搜索子树的大小,2)右树整体是否为搜索二叉树,3)右树上的最小值 隐含信息:如果左树整体就是搜索二叉树,那么左树上最大二叉搜索子树大小就是整颗子树的大小,也就是左树的大小!!!右树也一样!!!

这种情况下左树和右树的要求不一样,所以需要求全集。因为递归函数根本不要对左树的要求和右树的要求做区分。只要一劳永逸地写一份代码套上所有的东西,所以对左树和右树的要求编成一样的就行了。

所以只要是子树,都需要返回4个信息:

  • 最大二叉搜索子树的大小
  • 子树整体是否是搜索二叉树
  • 整棵子树的最大值
  • 整颗子树的最小值
// 任何子树都需要返回的信息
public static class Info{
	public boolean isAllBST;// 整体是否是搜索二叉树
	public int maxSubBSTSize;// 最大二叉搜索子树的大小
	public int max;// 整棵树的最大值
	public int min;// 整棵树的最小值
	
	public Info(boolean is,int size,int ma,int mi){
		isAllBST=is;
		maxSubBSTSize=size;
		max=ma;
		min=mi;
	}
}

完整代码:

package com.harrison.class08;

import java.util.ArrayList;

public class Code03_MaxSubBSTSize {
	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	// 任何子树都需要返回的信息
	public static class Info {
		public boolean isAllBST;// 整体是否是搜索二叉树
		public int maxSubBSTSize;// 最大二叉搜索子树的大小
		public int max;// 整棵树的最大值
		public int min;// 整棵树的最小值

		public Info(boolean is, int size, int ma, int mi) {
			isAllBST = is;
			maxSubBSTSize = size;
			max = ma;
			min = mi;
		}
	}
	
	public static Info process1(Node head) {
		// 空树上的最大值和最小值不好设置
		// 所以返回空的话,后面用到这个信息的时候就要先判空
		if(head==null) {
			return null;
		}
		Info leftInfo=process1(head.left);// 假设左树可以返回四个信息
		Info rightInfo=process1(head.right);// 假设右树可以返回四个信息
		// head结点的值在整棵树上,需要参与决策整棵树上的最大值和最小值
		int max=head.value;
		int min=head.value;
		// 左树不为空的话,左树上的最大值可以参与决策整棵树上的最大值
		// 同理,左树上的最小值也可以参与决策整棵树上的最小值,右树上也这么干
		if(leftInfo!=null) {
			max=Math.max(max, leftInfo.max);
			min=Math.min(min, leftInfo.min);
		}
		if(rightInfo!=null) {
			max=Math.max(max, rightInfo.max);
			min=Math.min(min, rightInfo.min);
		}
		// 第一种可能性:与head节点无关,要找的最大二叉搜索子树不以head为头结点
		// 所以这中情况下,左树和右树上的信息都能用
		int maxSubBSTSize=0;// 先默认整颗树的最大二叉搜索子树大小为0
		if(leftInfo!=null) {// 左树不为空的话,左树的答案就是目前的答案
			maxSubBSTSize=leftInfo.maxSubBSTSize;
		}
		if(rightInfo!=null) {// 右树不为空的话,当前答案和右树上的答案取最大值
			maxSubBSTSize=Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
		}
		// 第二种可能性,先假设不成立
		boolean isAllBST=false;
		// 如果可能性2成立,就设置两个变量
		// maxSubBSTSize=以head为头的所有结点数
		// isAllBST=true
		// 因为可能性2成立的话,整棵树就是二叉搜索树
		
		// 左树为空左树必是搜索二叉树,不为空的话,拿左树上的信息来用,右树同理
		// 左树为空并不影响二叉搜索树的性质,为空则拿左树的信息来用,右树同理
		if(
			(leftInfo==null?true:leftInfo.isAllBST)	
			&&
			(rightInfo==null?true:rightInfo.isAllBST) 
			&&
			(leftInfo==null?true:leftInfo.max<head.value)
			&&
			(rightInfo==null?true:rightInfo.min>head.value)
		  ) {
			// 左树整体已经是搜索二叉树的话,左树上最大二叉搜索子树的大小就是左树的大小
			// 右树同理
			maxSubBSTSize=(leftInfo==null?0:leftInfo.maxSubBSTSize)
						  + 
					      (rightInfo==null?0:rightInfo.maxSubBSTSize) 
					      +
					      1;
			isAllBST=true;
		}
		
		return new Info(isAllBST,maxSubBSTSize,max,min);
	}
	
	public static int maxSubBSTSize1(Node head) {
		if(head==null) {
			return 0;
		}
		return process1(head).maxSubBSTSize;
	}
	
	public static int getBSTSize(Node head) {
		if (head == null) {
			return 0;
		}
		ArrayList<Node> arr = new ArrayList<>();
		in(head, arr);
		for (int i = 1; i < arr.size(); i++) {
			if (arr.get(i).value <= arr.get(i - 1).value) {
				return 0;
			}
		}
		return arr.size();
	}

	public static void in(Node head, ArrayList<Node> arr) {
		if (head == null) {
			return;
		}
		in(head.left, arr);
		arr.add(head);
		in(head.right, arr);
	}

	public static int maxSubBSTSize2(Node head) {
		if (head == null) {
			return 0;
		}
		int h = getBSTSize(head);
		if (h != 0) {
			return h;
		}
		return Math.max(maxSubBSTSize2(head.left), maxSubBSTSize2(head.right));
	}
	
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	public static Node generate(int level, int maxLevel, int maxValue) {
		if (level > maxLevel || Math.random() < 0.5) {
			return null;
		}
		Node head = new Node((int) (Math.random() * maxValue));
		head.left = generate(level + 1, maxLevel, maxValue);
		head.right = generate(level + 1, maxLevel, maxValue);
		return head;
	}

	public static void main(String[] args) {
		int maxLevel = 4;
		int maxValue = 100;
		int testTimes = 1000000;
		for (int i = 0; i < testTimes; i++) {
			Node head = generateRandomBST(maxLevel, maxValue);
			if (maxSubBSTSize1(head) != maxSubBSTSize2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}
}