《演算法筆記》8. 二叉樹的遞迴思維實戰

Inky發表於2020-07-31

1 二叉樹的遞迴套路

1、 可以解決面試中的絕大部分二叉樹(95%以上)的問題,尤其是樹形dp問題

2、 其本質是利用遞迴遍歷二叉樹的便利性,每個節點在遞迴的過程中可以回到該節點3次

具體步驟為:

  1. 假設以X節點為頭,假設可以向X左樹和右樹要任何資訊
  2. 在上一步的假設下,討論以X為頭結點的樹,得到答案的可能性(最重要),常見分類是與X無關的答案,與X有關的答案
  3. 列出所有可能性後,確定到底需要向左樹和右樹要什麼樣的資訊
  4. 把左樹資訊和右樹資訊求全集,就是任何一顆子樹都需要返回的資訊S
  5. 遞迴函式都返回S,每顆子樹都這麼要求
  6. 寫程式碼,在程式碼中考慮如何把左樹資訊和右樹資訊整合出整棵樹的資訊

1.1 二叉樹的遞迴套路深度實踐

1.1.1 例一:判斷二叉樹平衡與否

給定一棵二叉樹的頭結點head,返回這顆二叉樹是不是平衡二叉樹

平衡樹概念:在一棵二叉樹中,每一個子樹,左樹的高度和右樹的高度差不超過1

那麼如果以X為頭的這顆樹,要做到平衡,那麼X的左樹要是平衡的,右樹也是平衡的,且X的左樹高度和右樹高度差不超過1

所以該題,我們X需要向左右子樹要的資訊為,1.高度 2. 是否平衡

package class08;

public class Code01_IsBalanced {

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

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

	public static boolean isBalanced1(Node head) {
		boolean[] ans = new boolean[1];
		ans[0] = true;
		process1(head, ans);
		return ans[0];
	}

	public static int process1(Node head, boolean[] ans) {
		if (!ans[0] || head == null) {
			return -1;
		}
		int leftHeight = process1(head.left, ans);
		int rightHeight = process1(head.right, ans);
		if (Math.abs(leftHeight - rightHeight) > 1) {
			ans[0] = false;
		}
		return Math.max(leftHeight, rightHeight) + 1;
	}

 	public static boolean isBalanced2(Node head) {
		return process2(head).isBalaced;
	}

	// 左、右要求一樣,Info 表示資訊返回的結構體
	public static class Info {
	        // 是否平衡
		public boolean isBalaced;
		// 高度多少
		public int height;

		public Info(boolean b, int h) {
			isBalaced = b;
			height = h;
		}
	}

        // 遞迴呼叫,X自身也要返回資訊Info。
        // 解決X節點(當前節點)怎麼返回Info資訊
	public static Info process2(Node X) {
	    // base case
		if (X == null) {
			return new Info(true, 0);
		}
		// 得到左樹資訊
		Info leftInfo = process2(X.left);
		// 得到右樹資訊
		Info rightInfo = process2(X.right);
		
		// 高度等於左右最大高度,加上當前頭結點的高度1
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		boolean isBalanced = true;
		// 左樹不平衡或者右樹不平衡,或者左右兩子樹高度差超過1
		// 那麼當前節點為頭的樹,不平衡
		if (!leftInfo.isBalaced || !rightInfo.isBalaced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
			isBalanced = false;
		}
		// 加工出當前節點的資訊返回
		return new Info(isBalanced, height);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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 = 5;
		int maxValue = 100;
		int testTimes = 1000000;
		for (int i = 0; i < testTimes; i++) {
			Node head = generateRandomBST(maxLevel, maxValue);
			if (isBalanced1(head) != isBalanced2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.2 例二:返回二叉樹任意兩個節點最大值

給定一棵二叉樹的頭結點head,任何兩個節點之間都存在距離,返回整棵二叉樹的最大距離

1、有可能最大距離和當前節點X無關,即最大距離是X左樹的最大距離,或者右樹的最大距離

2、最大距離跟X有關,即最大距離通過X。左樹離X最遠的點,到X右樹上離X最遠的點。即X左樹的高度加上X自身高度1,加上X右樹上的高度

結論:那麼根據遞迴套路,我們每次遞迴,需要返回X左樹的最大距離和高度,同理返回X右樹的最大距離和高度。Info包含最大距離和高度

package class08;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class Code08_MaxDistance {

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

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

	public static int maxDistance1(Node head) {
		if (head == null) {
			return 0;
		}
		ArrayList<Node> arr = getPrelist(head);
		HashMap<Node, Node> parentMap = getParentMap(head);
		int max = 0;
		for (int i = 0; i < arr.size(); i++) {
			for (int j = i; j < arr.size(); j++) {
				max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j)));
			}
		}
		return max;
	}

	public static ArrayList<Node> getPrelist(Node head) {
		ArrayList<Node> arr = new ArrayList<>();
		fillPrelist(head, arr);
		return arr;
	}

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

	public static HashMap<Node, Node> getParentMap(Node head) {
		HashMap<Node, Node> map = new HashMap<>();
		map.put(head, null);
		fillParentMap(head, map);
		return map;
	}

	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 int distance(HashMap<Node, Node> parentMap, Node o1, Node o2) {
		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);
		}
		Node lowestAncestor = cur;
		cur = o1;
		int distance1 = 1;
		while (cur != lowestAncestor) {
			cur = parentMap.get(cur);
			distance1++;
		}
		cur = o2;
		int distance2 = 1;
		while (cur != lowestAncestor) {
			cur = parentMap.get(cur);
			distance2++;
		}
		return distance1 + distance2 - 1;
	}

	public static int maxDistance2(Node head) {
		return process(head).maxDistance;
	}

        // 我們的資訊,整棵樹的最大距離和整棵樹的高度
	public static class Info {
		public int maxDistance;
		public int height;

		public Info(int dis, int h) {
			maxDistance = dis;
			height = h;
		}
	}
    
        // 以X節點為頭
	public static Info process(Node X) {
	        // base case
	 	if (X == null) {
			return new Info(0, 0);
		}
		// 預設從左樹拿到我們需要的info
		Info leftInfo = process(X.left);
		// 預設從右樹拿到我們需要的info
		Info rightInfo = process(X.right);
		// 用左右樹的資訊,加工自身的info
		// 自身的高度是,左右較大的高度加上自身節點高度1
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		// 自身最大距離,是左右樹最大距離和左右樹高度相加再加1,求最大值
		int maxDistance = Math.max(
				Math.max(leftInfo.maxDistance, rightInfo.maxDistance),
				leftInfo.height + rightInfo.height + 1);
		// 自身的info返回
		return new Info(maxDistance, height);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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 (maxDistance1(head) != maxDistance2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.3 例三:返回二叉樹中的最大二叉搜尋樹Size

給定個一顆二叉樹的頭結點head,返回這顆二叉樹中最大的二叉搜尋樹的Size

搜尋二叉樹概念:整顆樹上沒有重複值,左樹的值都比我小,右樹的值都比我大。每顆子樹都如此。

遞迴套路。1、與當前節點X無關,即最終找到的搜尋二叉樹,不以X為頭

2、與X有關,那麼X的左樹整體是搜尋二叉樹,右樹同理,且左樹的最大值小於X,右樹的最小值大於X

package class08;

import java.util.ArrayList;

public class Code04_MaxSubBSTSize {

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

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

	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 maxSubBSTSize1(Node head) {
		if (head == null) {
			return 0;
		}
		int h = getBSTSize(head);
		if (h != 0) {
			return h;
		}
		return Math.max(maxSubBSTSize1(head.left), maxSubBSTSize1(head.right));
	}

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

//	public static Info process(Node head) {
//		if (head == null) {
//			return null;
//		}
//		Info leftInfo = process(head.left);
//		Info rightInfo = process(head.right);
//		int min = head.value;
//		int max = head.value;
//		int maxSubBSTSize = 0;
//		if (leftInfo != null) {
//			min = Math.min(min, leftInfo.min);
//			max = Math.max(max, leftInfo.max);
//			maxSubBSTSize = Math.max(maxSubBSTSize, leftInfo.maxSubBSTSize);
//		}
//		if (rightInfo != null) {
//			min = Math.min(min, rightInfo.min);
//			max = Math.max(max, rightInfo.max);
//			maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
//		}
//		boolean isBST = false;
//		if ((leftInfo == null ? true : (leftInfo.isAllBST && leftInfo.max < head.value))
//				&& (rightInfo == null ? true : (rightInfo.isAllBST && rightInfo.min > head.value))) {
//			isBST = true;
//			maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
//					+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
//		}
//		return new Info(isBST, maxSubBSTSize, min, max);
//	}

	// 任何子樹,都返回4個資訊
	public static class Info {
	        // 整體是否是二叉搜尋樹
		public boolean isAllBST;
		// 最大的滿足二叉搜尋樹樹條件的size
		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;
		}
	}

        // 以X為頭
	public static Info process(Node X) {
	        // base case
		if(X == null) {
			return null;
		}
		// 預設左樹可以給我info資訊
		Info leftInfo = process(X.left);
		// 預設右樹可以給我info資訊
		Info rightInfo = process(X.right);

                // 通過左右樹給我的資訊,加工我自己的info

		int min = X.value;
		int max = X.value;
		
		// 左樹不為空,加工min和max
		if(leftInfo != null) {
			min = Math.min(min, leftInfo.min);
			max = Math.max(max, leftInfo.max);
		}
		// 右樹不為空,加工min和max
		if(rightInfo != null) {
			min = Math.min(min, rightInfo.min);
			max = Math.max(max, rightInfo.max);
		}

                // 可能性1與X無關的情況
		int maxSubBSTSize = 0;
		if(leftInfo != null) {
			maxSubBSTSize = leftInfo.maxSubBSTSize;
		}
		if(rightInfo !=null) {
			maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
		}
		
		// 可能性2,與X有關
		boolean isAllBST = false;

		if(
				// 左樹和人右樹整體需要是搜尋二叉樹
				(  leftInfo == null ? true : leftInfo.isAllBST    )
				&&
				(  rightInfo == null ? true : rightInfo.isAllBST    )
				&&
				// 左樹最大值<X,右樹最小值>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);
	}
	
	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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!");
	}

}

1.1.4 例四:派對最大快樂值

排隊最大快樂值問題,員工資訊定義如下,多叉樹結構:

class Employee{
    // 這名員工可以帶來的快樂值
    public int happy;
    // 這名員工有哪些直接的下級
    List<Employee> subordinates;
}

每個員工都符合Employee類的描述,整個公司的人員結構可以看作是一顆標準的,沒有環的多叉樹。樹的頭結點是公司唯一的老闆。除了老闆外的每個員工都有唯一的直接上級。葉節點是沒有任何下屬的基層員工(subordinates為空),除了基層員工股外,每個員工都有一個或多個直接下級。

現在公司要來辦party,你可以決定哪些員工來,哪些員工不來,規則:

1、 如果某個員工來了,那麼這個員工的所有直接下級都不能來

2、 排隊的整體快樂值是所有到場員工的快樂值的累加

3、 你的目標是讓排隊的整體快樂值儘量的大

給定一顆多叉樹頭結點boss,請返回排隊的最大快樂值

思路:根據X來與不來分類

如果X來,我們能獲得X的快樂值X.happy。X的直接子不能來,但我們能拿到X某個子樹整棵樹的的最大快樂值

如果X不來,不發請柬,我們能獲得X的快樂值為0,X直接子樹頭結點來或者不來求最大值...

package class08;

import java.util.ArrayList;
import java.util.List;

public class Code09_MaxHappy {

        // 員工對應的多叉樹節點結構
	public static class Employee {
		public int happy;
		public List<Employee> nexts;

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

	}

	public static int maxHappy1(Employee boss) {
		if (boss == null) {
			return 0;
		}
		return process1(boss, false);
	}

	public static int process1(Employee cur, boolean up) {
		if (up) {
			int ans = 0;
			for (Employee next : cur.nexts) {
				ans += process1(next, false);
			}
			return ans;
		} else {
			int p1 = cur.happy;
			int p2 = 0;
			for (Employee next : cur.nexts) {
				p1 += process1(next, true);
				p2 += process1(next, false);
			}
			return Math.max(p1, p2);
		}
	}

	public static int maxHappy2(Employee boss) {
		if (boss == null) {
			return 0;
		}
		Info all = process2(boss);
		return Math.max(all.yes, all.no);
	}

        // 遞迴資訊
	public static class Info {
	        // 頭結點在來的情況下整棵樹的最大值
		public int yes;
		// 頭結點在不來的情況下整棵樹的最大值
		public int no;

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

	public static Info process2(Employee x) {
	        // base case 基層員工
		if (x.nexts.isEmpty()) {
			return new Info(x.happy, 0);
		}
		// 當前X來的初始值
		int yes = x.happy;
		// 當前X不來的初始值
		int no = 0;
		// 每棵子樹呼叫遞迴資訊
		for (Employee next : x.nexts) {
			Info nextInfo = process2(next);
			// 根據子樹的遞迴返回的資訊,加工自身的info
			// 如果X來,子不來
			yes += nextInfo.no;
			// 如果X不來,子不確定來不來
			no += Math.max(nextInfo.yes, nextInfo.no);
		}
		return new Info(yes, no);
	}

	// for test
	public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) {
		if (Math.random() < 0.02) {
			return null;
		}
		Employee boss = new Employee((int) (Math.random() * (maxHappy + 1)));
		genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy);
		return boss;
	}

	// for test
	public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) {
		if (level > maxLevel) {
			return;
		}
		int nextsSize = (int) (Math.random() * (maxNexts + 1));
		for (int i = 0; i < nextsSize; i++) {
			Employee next = new Employee((int) (Math.random() * (maxHappy + 1)));
			e.nexts.add(next);
			genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy);
		}
	}

	public static void main(String[] args) {
		int maxLevel = 4;
		int maxNexts = 7;
		int maxHappy = 100;
		int testTimes = 100000;
		for (int i = 0; i < testTimes; i++) {
			Employee boss = genarateBoss(maxLevel, maxNexts, maxHappy);
			if (maxHappy1(boss) != maxHappy2(boss)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.5 例五:判斷二叉樹是否是滿二叉樹

給定一棵二叉樹的頭結點head,返回這顆二叉樹是不是滿二叉樹。

思路:滿二叉樹一定滿足2^L - 1 == N,其中L是這顆二叉樹的高度,N是這顆二叉樹的節點個數

package class08;

public class Code02_IsFull {

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

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

	public static boolean isFull1(Node head) {
		if (head == null) {
			return true;
		}
		int height = h(head);
		int nodes = n(head);
		return (1 << height) - 1 == nodes;
	}

	public static int h(Node head) {
		if (head == null) {
			return 0;
		}
		return Math.max(h(head.left), h(head.right)) + 1;
	}

	public static int n(Node head) {
		if (head == null) {
			return 0;
		}
		return n(head.left) + n(head.right) + 1;
	}

	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);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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 = 5;
		int maxValue = 100;
		int testTimes = 1000000;
		for (int i = 0; i < testTimes; i++) {
			Node head = generateRandomBST(maxLevel, maxValue);
			if (isFull1(head) != isFull2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.6 例六:二叉搜尋樹的頭結點

給定一棵二叉樹的頭結點head,返回這顆二叉樹中最大的二叉搜尋子樹的頭節點

和前文的返回二叉搜尋子樹的Size問題類似

package class08;

import java.util.ArrayList;

public class Code05_MaxSubBSTHead {

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

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

	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 Node maxSubBSTHead1(Node head) {
		if (head == null) {
			return null;
		}
		if (getBSTSize(head) != 0) {
			return head;
		}
		Node leftAns = maxSubBSTHead1(head.left);
		Node rightAns = maxSubBSTHead1(head.right);
		return getBSTSize(leftAns) >= getBSTSize(rightAns) ? leftAns : rightAns;
	}

	public static Node maxSubBSTHead2(Node head) {
		if (head == null) {
			return null;
		}
		return process(head).maxSubBSTHead;
	}

	// 每一棵子樹Info
	public static class Info {
		public Node maxSubBSTHead;
		public int maxSubBSTSize;
		public int min;
		public int max;

		public Info(Node h, int size, int mi, int ma) {
			maxSubBSTHead = h;
			maxSubBSTSize = size;
			min = mi;
			max = ma;
		}
	}

	public static Info process(Node X) {
		if (X == null) {
			return null;
		}
		Info leftInfo = process(X.left);
		Info rightInfo = process(X.right);
		int min = X.value;
		int max = X.value;
		Node maxSubBSTHead = null;
		int maxSubBSTSize = 0;
		if (leftInfo != null) {
			min = Math.min(min, leftInfo.min);
			max = Math.max(max, leftInfo.max);
			maxSubBSTHead = leftInfo.maxSubBSTHead;
			maxSubBSTSize = leftInfo.maxSubBSTSize;
		}
		if (rightInfo != null) {
			min = Math.min(min, rightInfo.min);
			max = Math.max(max, rightInfo.max);
			if (rightInfo.maxSubBSTSize > maxSubBSTSize) {
				maxSubBSTHead = rightInfo.maxSubBSTHead;
				maxSubBSTSize = rightInfo.maxSubBSTSize;
			}
		}
		if ((leftInfo == null ? true : (leftInfo.maxSubBSTHead == X.left && leftInfo.max < X.value))
				&& (rightInfo == null ? true : (rightInfo.maxSubBSTHead == X.right && rightInfo.min > X.value))) {
			maxSubBSTHead = X;
			maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
					+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
		}
		return new Info(maxSubBSTHead, maxSubBSTSize, min, max);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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 (maxSubBSTHead1(head) != maxSubBSTHead2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.7 例子七:是否是完全二叉樹

給定一棵二叉樹的頭結點head,返回這顆二叉樹是不是完全二叉樹

完全二叉樹概念在堆的章節,有介紹。

寬度優先遍歷解決思路:

1、如果用樹的寬度優先遍歷的話,如果某個節點有右孩子,但是沒有左孩子,一定不是完全二叉樹

2、在1條件的基礎上,一旦遇到第一個左右孩子不雙全的節點,後續所有節點必須為葉子節點

二叉樹遞迴套路解法思路:

1、滿二叉樹(無缺口),一定是完全二叉樹。此時左右樹需要給X的資訊是,是否是滿的和高度。如果左右樹滿,且左右樹高度一樣,那麼是該種情況--滿二叉樹

2、有缺口,1缺口可能停在我的左樹上。左樹需要給我是否是完全二叉樹,右樹需要給X是否是滿二叉樹,且左樹高度比右樹高度大1

3、缺口可能在左右樹的分界。左樹是滿的,右樹也是滿的,左樹高度比右樹大1

4、左樹已經滿了,缺口可能在我的右樹上。左樹是滿的,右樹是完全二叉樹,且左右樹高度一樣

所以我們的遞迴時,需要向子樹要的資訊為:是否是完全二叉樹,是否是滿二叉樹,高度

package class08;

import java.util.LinkedList;

public class Code06_IsCBT {

	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;
	}

        // 遞迴的解法
	public static boolean isCBT2(Node head) {
		if (head == null) {
			return true;
		}
		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;
		}
	}

        // 對於任何節點,我們要返回三個元素組成的Info
	public static Info process(Node X) {
	        // 如果是空樹,我們封裝Info而不是返回為空
	        // 方便下文不需要額外增加判空處理
		if (X == null) {
			return new Info(true, true, 0);
		}
		// 左樹info
		Info leftInfo = process(X.left);
		// 右樹info
		Info rightInfo = process(X.right);
		
		// 接下來整合當前X的Info
		
		// 高度資訊=左右樹最大高度值+1
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		
		// X是否是滿二叉樹資訊=左右都是滿且左右高度一樣
		boolean isFull = leftInfo.isFull 
				&& 
				rightInfo.isFull 
				&& leftInfo.height == rightInfo.height;
		
		// X是否是完全二叉樹
		boolean isCBT = false;
		// 滿二叉樹是完全二叉樹
		if (isFull) {
			isCBT = true;
		// 以x為頭整棵樹,不滿
		} else { 
		    // 左右都是完全二叉樹才有討論的必要
			if (leftInfo.isCBT && rightInfo.isCBT) {
				// 第二種情況
				if (leftInfo.isCBT 
						&& rightInfo.isFull 
						&& leftInfo.height == rightInfo.height + 1) {
					isCBT = true;
				}
				// 第三種情況
				if (leftInfo.isFull 
						&& 
						rightInfo.isFull 
						&& leftInfo.height == rightInfo.height + 1) {
					isCBT = true;
				}
				// 第四種情況
				if (leftInfo.isFull 
						&& rightInfo.isCBT && leftInfo.height == rightInfo.height) {
					isCBT = true;
				}
				
			}
		}
		return new Info(isFull, isCBT, height);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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 = 5;
		int maxValue = 100;
		int testTimes = 1000000;
		for (int i = 0; i < testTimes; i++) {
			Node head = generateRandomBST(maxLevel, maxValue);
			if (isCBT1(head) != isCBT2(head)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}

}

1.1.8 例子八:最低公共祖先

給次那個一顆二叉樹的頭結點head,和另外兩個節點a和b。返回a和b的最低公共祖先

二叉樹的最低公共祖先概念: 任意兩個節點,往父親看,最開始交匯的節點,就是最低公共祖先

解法一:用輔助map,Key表示節點,Value表示節點的父親節點。我們把兩個目標節點的父親以此放到map中,依次遍歷

解法二:使用二叉樹的遞迴套路。

1、o1和o2都不在以X為頭的樹上

2、o1和o2有一個在以X為頭的樹上

3、o1和o2都在以X為頭的樹上

3.1、X為頭的樹,左樹右樹各有一個

3.2、X為頭的樹,左樹含有o1和o2

3.3、X為頭的樹,右樹含有o1和o2

4、X自身就是o1或者o2,即如果X是o1那麼左右樹收集到o2即可,如果X是o2,左右樹收集到o1即可。

package class08;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class Code07_lowestAncestor {

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

		public Node(int data) {
			this.value = data;
		}
	}
    
        // 解法1,藉助輔助Map和Set
	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);
		// 遞迴填充map
		fillParentMap(head, parentMap);
		// 輔助set
		HashSet<Node> o1Set = new HashSet<>();
		Node cur = o1;
		o1Set.add(cur);
		// o1Set存入的是沿途所有的父節點
		while (parentMap.get(cur) != null) {
			cur = parentMap.get(cur);
			o1Set.add(cur);
		}
		cur = o2;
		// o2的某個父節點在o1Set中,就是我們要找的節點
		while (!o1Set.contains(cur)) {
			cur = parentMap.get(cur);
		}
		return cur;
	}

	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);
		}
	}

        // 解法1,二叉樹遞迴套路解法
	public static Node lowestAncestor2(Node head, Node o1, Node o2) {
		return process(head, o1, o2).ans;
	}

	// 任何子樹需要的資訊結構
	public static class Info {
	        // o1和o2的最初交匯點,如果不是在當前這顆X節點的樹上,返回空
		public Node ans;
		// 在當前子樹上,是否發現過o1和o2
		public boolean findO1;
		public boolean findO2;

		public Info(Node a, boolean f1, boolean f2) {
			ans = a;
			findO1 = f1;
			findO2 = f2;
		}
	}

	public static Info process(Node X, Node o1, Node o2) {
	        // o1和o2不為空,那麼空樹上的Info如下
		if (X == null) {
			return new Info(null, false, false);
		}
		// 左樹返回的Info
		Info leftInfo = process(X.left, o1, o2);
		// 右樹返回的Info
		Info rightInfo = process(X.right, o1, o2);
		
		// 構建X自身需要返回的Info
		// X為頭的樹上是否發現了o1
		boolean findO1 = X == o1 || leftInfo.findO1 || rightInfo.findO1;
		// X為頭的樹上是否發現了o2
		boolean findO2 = X == o2 || leftInfo.findO2 || rightInfo.findO2;
		// 	O1和O2最初的交匯點在哪?

		// 1) 在左樹上已經提前交匯了,最初交匯點保留左樹的
		Node ans = null;
		if (leftInfo.ans != null) {
			ans = leftInfo.ans;
		}
		// 2) 在右樹上已經提前交匯了,最初交匯點保留右樹的
		if (rightInfo.ans != null) {
			ans = rightInfo.ans;
		}
		// 3) 沒有在左樹或者右樹上提前交匯
		if (ans == null) {
		        // 但是找到了o1和o2,那麼交匯點就是X自身
			if (findO1 && findO2) {
				ans = X;
			}
		}
		return new Info(ans, findO1, findO2);
	}

	// for test
	public static Node generateRandomBST(int maxLevel, int maxValue) {
		return generate(1, maxLevel, maxValue);
	}

	// for test
	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;
	}

	// for test
	public static Node pickRandomOne(Node head) {
		if (head == null) {
			return null;
		}
		ArrayList<Node> arr = new ArrayList<>();
		fillPrelist(head, arr);
		int randomIndex = (int) (Math.random() * arr.size());
		return arr.get(randomIndex);
	}

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

	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);
			Node o1 = pickRandomOne(head);
			Node o2 = pickRandomOne(head);
			if (lowestAncestor1(head, o1, o2) != lowestAncestor2(head, o1, o2)) {
				System.out.println("Oops!");
			}
		}
		System.out.println("finish!");
	}
}

二叉樹的遞迴套路,最終轉化為基於X只找可能性即可。即樹形DP問題

相關文章