树型递归思想

前言

本文章将从很多涉及二叉树来解的算法题中,高度抽象出来一套底层思想,掌握了六步法,可以解决更多算法题。


二叉树递归思想

1)假设以X节点为头,假设可以向X左数和右数要任何信息
2)在上一步的假设下,讨论以X为头节点的数,得到答案的可能性(简单情况是两种分类)

  • 和X有关的答案
  • 和X无关的答案

3)列出所有可能性后,确定到底需要向左树和右树要什么样的信息
4)把左树信息和右数信息求全集,就是任何一颗子树都需要返回的信息S
5)递归函数都返回S,每一棵子树都这么要求
6)写代码,在代码中考虑如何把左树的信息和右数的信息整合出整棵树的信息

下面将以例题进行渗透!


1.判断平衡二叉树为例。

需要子树的信息:

1.左子树是否为平衡二叉树
2.右子树是否为平衡二叉树
3.左子树的高度
4.右子树的高度

代码

	//结构体
	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 isBalanced;
		public int height;
		
		public Info(boolean i, int h) {
			isBalanced = i;
			height = h;
		}
	}
	//判断
	public static boolean isBalanced2(Node head) {
		return process(head).isBalanced;
	}
	public static Info process(Node x) {
		if(x == null) {
			return new Info(true, 0);
		}
		Info leftInfo = process(x.left);		//左树提供信息
		Info rightInfo = process(x.right);		//右数提供信息
		int height = Math.max(leftInfo.height, rightInfo.height)  + 1;	//加工自己的高度信息
		boolean isBalanced = true;				//加工自己的平衡信息
		if(!leftInfo.isBalanced) {
			isBalanced = false;					//加工自己的平衡信息
		}
		if(!rightInfo.isBalanced) {
			isBalanced = false;					//加工自己的平衡信息
		}
		if(Math.abs(leftInfo.height - rightInfo.height) > 1) {
			isBalanced = false;					//加工自己的平衡信息
		}
		return new Info(isBalanced, height);	//提交信息
	}

2.寻找最大的搜索子树为例。

需要子树的信息:

1.左子树是否搜索树
2.右子树是否为搜索树
3.左子树最大值不超过父节点(X)值
4.右子树最小值不小于父节点(X)值
5.左子树节点数
6.右孩子节点数
注意:
情况1和2都包含的:最大值,最小值
情况1,与X(父节点)无关的答案,即X不是搜索树。
情况2,与X有关,即满足上面前四个条件,就需要的信息:

  • X是搜索树
  • 最大搜索子树的大小:X的节点个数(左右子树节点数之和再加上自身)

代码

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

        public Node(int data) {
            this.value = data;
        }
    }
    public static int maxSubBSTSize2(Node head) {
		if (head == null) {
			return 0;
		}
		return process(head).maxSubBSTSize;
	}

	// 任何子树
	public static class Info {
		public boolean isAllBST;
		public int maxSubBSTSize;
		public int min;
		public int max;

		public Info(boolean is, int size, int mi, int ma) {
			isAllBST = is;
			maxSubBSTSize = size;
			min = mi;
			max = ma;
		}
	}

	public static Info process(Node X) {
		if(X == null) {
			return null;
		}
		//加工自己的max,min
		Info leftInfo = process(X.left);
		Info rightInfo = process(X.right);

		int min = X.value;
		int max = X.value;

		if(leftInfo != null) {
			min = Math.min(min, leftInfo.min);
			max = Math.max(max, leftInfo.max);
		}
		if(rightInfo != null) {
			min = Math.min(min, rightInfo.min);
			max = Math.max(max, rightInfo.max);
		}

		//加工自己的节数量maxSubBSTSize和判断是否为搜索树isAllBST
		
		int maxSubBSTSize = 0;
		if(leftInfo != null) {
			maxSubBSTSize = leftInfo.maxSubBSTSize;
		}
		if(rightInfo !=null) {
			maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
		}
		//开始分情况
		boolean isAllBST = false;
		
		if(
				// 左树整体需要是搜索二叉树
				(  leftInfo == null ? true : leftInfo.isAllBST    )
				&&
				(  rightInfo == null ? true : rightInfo.isAllBST    )
				&&
				// 左树最大值<x
				(leftInfo == null ? true : leftInfo.max < X.value)
				&&
				(rightInfo == null ? true : rightInfo.min > X.value)


				) {

			maxSubBSTSize =
					(leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
					+
					(rightInfo == null ? 0 : rightInfo.maxSubBSTSize)
					+
					1;
					isAllBST = true;


		}
		return new Info(isAllBST, maxSubBSTSize, min, max);
	}

3.派对的最大快乐

员工信息的定义如下:

class Employee {
	public int happy;//这名员工可以带来的快乐值
	List<Employee> subordinates;//这名员工有哪些直接下级
}

公司的每个员工都有符合Employee 类的描述。整个公司的人员结构可以看作是一颗标准的,没有环的多叉树。树的头节点是公司的唯一老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),出基层员工外,每个员工都有一个或镀铬直接下级。
规则:
1.如果某个员工来了,那么这个员工的所有直接下级都不能来
2.派对的快乐值是所有在场的累加
给定一棵树多叉树的头节点boss,请返回派对的最大快乐值。

例如:
boss:X,员工:a,b,c

情况1:X不来
happy =
0 +
max{(a来)子树a的最大值,(a不来)子树a的最大值} +
max{(b来)子树b的最大值,(b不来)子树b的最大值} +
max{(c来)子树c的最大值,(a不来)子树c的最大值}

情况2:X来
happy =
X +
(a不来)子树a的最大值 +
(b不来)子树b的最大值+
(c不来)子树c的最大值

需要的信息

1.在头节点来的时候,整颗子树的最大快乐。
2.在头节点不来的时候,整颗子树的最大快乐。

代码

public static class Employee {
		public int happy;
		public List<Employee> nexts;

		public Employee(int h) {
			happy = h;
			nexts = new ArrayList<>();
		}

	}
public static int maxHappy2(Employee head) {
		Info allInfo = process(head);
		return Math.max(allInfo.no, allInfo.yes);
	}

	public static class Info {
		public int no;
		public int yes;

		public Info(int n, int y) {
			no = n;
			yes = y;
		}
	}

	public static Info process(Employee x) {
		if (x == null) {
			return new Info(0, 0);
		}
		int no = 0;
		int yes = x.happy;
		for (Employee next : x.nexts) {
			Info nextInfo = process(next);
			no += Math.max(nextInfo.no, nextInfo.yes);
			yes += nextInfo.no;

		}
		return new Info(no, yes);
	}

4.判断是否为满二叉树

比较简单比分析了,直接上代码

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

		public Node(int data) {
			this.value = data;
		}
	}
public static boolean isFull2(Node head) {
		if (head == null) {
			return true;
		}
		Info all = process(head);
		return (1 << all.height) - 1 == all.nodes;
	}

	public static class Info {
		public int height;
		public int nodes;

		public Info(int h, int n) {
			height = h;
			nodes = n;
		}
	}

	public static Info process(Node head) {
		if (head == null) {
			return new Info(0, 0);
		}
		Info leftInfo = process(head.left);
		Info rightInfo = process(head.right);
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		int nodes = leftInfo.nodes + rightInfo.nodes + 1;
		return new Info(height, nodes);
	}

思想进阶(多情况)

5.判断是否为完全二叉树

判断标准:
1)任意节点有右孩子无左孩子,false
2)一旦遇到左右孩子不双全的节点,其后面的节点有非叶节点,false

非递归,宽度优先遍历解决

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

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

	public static boolean isCBT1(Node head) {
		if (head == null) {
			return true;
		}
		LinkedList<Node> queue = new LinkedList<>();
		// 是否遇到过左右两个孩子不双全的节点
		boolean leaf = false;
		Node l = null;
		Node r = null;
		queue.add(head);
		while (!queue.isEmpty()) {
			head = queue.poll();
			l = head.left;
			r = head.right;
			if (
			// 如果遇到了不双全的节点之后,又发现当前节点不是叶节点
			(leaf && (l != null || r != null)) || (l == null && r != null)

			) {
				return false;
			}
			if (l != null) {
				queue.add(l);
			}
			if (r != null) {
				queue.add(r);
			}
			if (l == null || r == null) {
				leaf = true;
			}
		}
		return true;
	}

递归思想解决

情况1:无缺口,满二叉树。需要信息:

  • 子树是否满的,以及高度

结论:当子树是满的,且高度一样,就可以确定整棵树完全二叉树

情况2:有缺口。

1)缺口在左子树,,需要信息:

  • 左子树是否为完全二叉树,右子树是否为满二叉树,和高度
    在这里插入图片描述

结论:左子树为完全,右子树为满,左比右高1层,就可以确定整棵树完全二叉树。

2)缺口在中间,需要信息:

  • 左子树是否为满二叉树,右子树是否为满二叉树,和高度
    在这里插入图片描述
    结论:左子树为满,右子树为满,左比右高1层,就可以确定整棵树完全二叉树

3)缺口在右子树,需要信息:

  • 左子树是否为满二叉树,右子树是否为完全二叉树,和高度
    在这里插入图片描述
    结论:左子树为满,右子树为满,左右一样高,就可以确定整棵树完全二叉树
	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}
	public static boolean isCBT2(Node head) {
		return process(head).isCBT;
	}
	//是否满的?是否完全?高度
	public static class Info {
		public boolean isFull;
		public boolean isCBT;
		public int height;

		public Info(boolean full, boolean cbt, int h) {
			isFull = full;
			isCBT = cbt;
			height = h;
		}
	}

	public static Info process(Node x) {
		if (x == null) {				//空树
			return new Info(true, true, 0);
		}
		Info leftInfo = process(x.left);		//左树提供信息
		Info rightInfo = process(x.right);
		//加工自己信息
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
		
		//开始分情况
		boolean isCBT = false;
		if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height) {//情况1
			isCBT = true;
		} else if (leftInfo.isCBT && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {//情况2的1)
			isCBT = true;
		} else if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {//情况2的2)
			isCBT = true;
		} else if (leftInfo.isFull && rightInfo.isCBT && leftInfo.height == rightInfo.height) {//情况2的3)
			isCBT = true;
		}
		return new Info(isFull, isCBT, height);
	}

6.两个节点的最低公共祖先

非递归,利用哈希表

思路:
建立map表,存储每个节点与父节点。
在利用set表,这个表来存储节点a的所有父节点(循环遍历map);然后将节点b和节点b的父节点(遍历map可得到)在set表里匹配。

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

		public Node(int data) {
			this.value = data;
		}
	}
	public static void fillParentMap(Node head, HashMap<Node, Node> parentMap) {
		if (head.left != null) {
			parentMap.put(head.left, head);
			fillParentMap(head.left, parentMap);
		}
		if (head.right != null) {
			parentMap.put(head.right, head);
			fillParentMap(head.right, parentMap);
		}
	}

	public static Node lowestAncestor1(Node head, Node o1, Node o2) {
		if (head == null) {
			return null;
		}
		// key的父节点是value
		HashMap<Node, Node> parentMap = new HashMap<>();
		parentMap.put(head, null);
		fillParentMap(head, parentMap);
		HashSet<Node> o1Set = new HashSet<>();
		Node cur = o1;
		o1Set.add(cur);
		while (parentMap.get(cur) != null) {
			cur = parentMap.get(cur);
			o1Set.add(cur);
		}
		cur = o2;
		while (!o1Set.contains(cur)) {
			cur = parentMap.get(cur);
		}
		return cur;
	}

递归思想解决

思路:
节点a,b与某一以X为头节点的树,三者之间无非三种关系:
情况1:a,b都不在X树上
情况2:a,b一个在X树上
情况3:都在X树上时,又分为:

  • 1) 左,右子树各一个
  • 2)都在左子树
  • 3) 都在右子树
  • 4)X就是其中之一,另一节点在左子树或右子树
	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 findA;
		public boolean findB;
		public Node ans;

		public Info(boolean fA, boolean fB, Node an) {
			findA = fA;
			findB = fB;
			ans = an;
		}
	}

	public static Info process(Node x, Node a, Node b) {
		if (x == null) {
			return new Info(false, false, null);
		}
		Info leftInfo = process(x.left, a, b);
		Info rightInfo = process(x.right, a, b);
		boolean findA = (x == a) || leftInfo.findA || rightInfo.findA;
		boolean findB = (x == b) || leftInfo.findB || rightInfo.findB;
		Node ans = null;
		//寻找最初交汇点
		if (leftInfo.ans != null) {				
			ans = leftInfo.ans;					//情况2)在左树上已经交汇了,都在左树
		} else if (rightInfo.ans != null) {
			ans = rightInfo.ans;				//情况3)在右树上已经交汇了,都在右数
		} else {
			if (findA && findB) {
				ans = x;						//情况1)和情况4)			}
		}
												//剩余情况1和情况2
		return new Info(findA, findB, ans);		
	}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Strive_LiJiaLe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值