算法与数据结构基础课第八节笔记

二叉树的递归套路

例1:如何设计一个直观打印整棵二叉树的函数

将原本的树逆时针转90度

右中左的顺序

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

例2:二叉树结构如下定义:
Class Node {
    V value;
    Node left;
    Node right;
    Node parent;
}
给你二叉树中的某个节点,返回该节点的后继节点(在中序遍历中的该节点的下一个节点)

普遍做法是先找到head,然后再中序遍历找后继节点

假设X节点有右树,那么后继节点一定是右树上的最左节点

没有右树,往上不断找父,这个父是他的父的左孩子,这个父就是后继节点

后继节点练习题

public static Node getSuccessorNode(Node node) {
		if (node == null) {
			return node;
		}
		if (node.right != null) {
			return getLeftMost(node.right);
		} else { // 无右子树
			Node parent = node.parent;
			while (parent != null && parent.left != node) { // 当前节点是其父亲节点右孩子
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	public static Node getLeftMost(Node node) {
		if (node == null) {
			return node;
		}
		while (node.left != null) {
			node = node.left;
		}
		return node;
	}

例3:

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次。 请从上到下打印所有折痕的方向。
例如:N=1时,打印: down N=2时,打印: down down up

实际对折可发现,每次对折后,老折痕的左边会出现down痕,右边会出现up痕,实际上就是一个完全二叉树构,从上往下打印也就是中序遍历

        public static void printAllFolds(int N) {
		printProcess(1, N, true);
	}

	// 递归过程,来到了某一个节点,
	// i是节点的层数,N一共的层数,down == true  凹    down == false 凸
	public static void printProcess(int i, int N, boolean down) {
		if (i > N) {
		   return;
		}
		printProcess(i + 1, N, true);
		System.out.println(down ? "凹 " : "凸 ");
		printProcess(i + 1, N, false);
	}

二叉树的递归套路

可以解决面试中绝大多数的二叉树问题尤其是树型dp问题

本质是利用递归遍历二叉树的便利性

  1. 假设以X节点为头,假设可以向X左树和X右树要任何信息
  2. 在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要)
  3. 列出所有可能性后,确定到底需要向左树和右树要什么样的信息
  4. 把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S
  5. 递归函数都返回S,每一棵子树都这么要求
  6. 写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息

 

例1:给定一棵二叉树的头节点head,返回这颗二叉树是不是平衡二叉树(左子树与右子树的高度差不超过1)

平衡二叉树

需要的信息:左平,右平,|左高-右高| < 2

        // 信息返回的结构体
	public static class Info {
		public boolean isBalaced;
		public int height;

		public Info(boolean b, int h) {
			isBalaced = b;
			height = h;
		}
	}
        public static boolean isBalanced2(Node head) {
		return process2(head).isBalaced;
	}

        public static Info process2(Node head) {
		if (head == null) {
			return new Info(true, 0);
		}
		Info leftInfo = process2(head.left);
		Info rightInfo = process2(head.right);
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		boolean isBalanced = true;
		if (!leftInfo.isBalaced || !rightInfo.isBalaced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
			isBalanced = false;
		}
		return new Info(isBalanced, height);
	}

 普通解法:

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

例2:给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离

二叉树的最大距离

分情况

1、与X无关,那么最大距离就是左树上的最大距离和右树上的最大距离取大

2、与X有关,就是左树上离X最远的点(子树高度)+右树上离X最远的点+1

因此需要的信息就是,左树的最大距离和高度,右树的最大距离和高度

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

		public Info(int dis, int h) {
			maxDistance = dis;
			height = h;
		}
	}
        public static int maxDistance2(Node head) {
		return process(head).maxDistance;
	}

        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 maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance),
				leftInfo.height + rightInfo.height + 1);
		return new Info(maxDistance, height);
	}

例3:给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的大小

二叉搜索子树

搜索二叉树就是没有重复值,左树上的值都比其头小,右树上的值都比其头大

对X分类:

与X无关,就是左子树上的最大二叉搜索子树的大小与右子树上的取大

与X有关,左子树和右子树都必须是二叉搜索树,左侧最大值要小于X,右侧最小值要大于X

需要的信息:

最大搜索子树大小,最大最小值,整体是不是二叉搜索树

 

      public static class Info {
		public boolean isBST;
		public int maxSubBSTSize;
		public int min;
		public int max;

		public Info(boolean is, int size, int mi, int ma) {
			isBST = is;
			maxSubBSTSize = size;
			min = mi;
			max = ma;
		}
	}
        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.isBST && leftInfo.max < head.value))
				&& (rightInfo == null ? true : (rightInfo.isBST && 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:派对的最大快乐值
 最大快乐值
员工信息的定义如下:
class Employee {
    public int happy; // 这名员工可以带来的快乐值
    List<Employee> subordinates; // 这名员工有哪些直接下级
}

公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、 没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。 叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。

这个公司现在要办party,你可以决定哪些员工来,哪些员工不来,规则:
1.如果某个员工来了,那么这个员工的所有直接下级都不能来
2.派对的整体快乐值是所有到场员工快乐值的累加
3.你的目标是让派对的整体快乐值尽量大
给定一棵多叉树的头节点boss,请返回派对的最大快乐值。

X来,他的直接下属们不来时,各自整颗树的最大快乐值

X不来,他的直接下属们来或者不来,各自整颗树的最大快乐值

       public static class Info {
		public int yes; // 头结点来的时候最大快乐值
		public int no;  // 头结点不来时最大快乐值

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


        public static Info process2(Employee x) {
		// 以基层员工作为终止条件
		if (x.nexts.isEmpty()) {
			return new Info(x.happy, 0);
		}
		int yes = x.happy;
		int no = 0;
		for (Employee next : x.nexts) {
			Info nextInfo = process2(next);
			yes += nextInfo.no;
			no += Math.max(nextInfo.yes, nextInfo.no);
		}
		return new Info(yes, no);
	}

例5:给定一棵二叉树的头节点head,返回这颗二叉树是不是满二叉树

 性质:高度为h的满二叉树,有(2^h)-1个结点

需要的信息是高度和节点数

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

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

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

例6:给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点

1、与X无关,左子树上是最大二叉搜索树或在右子树上

2、与X有关,看能不能和X连起来

需要的信息:

        // 每个子树
	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 Node maxSubBSTHead2(Node head) {
		if (head == null) {
			return null;
		}
		return process(head).maxSubBSTHead;
	}

        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;
		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 == head.left && leftInfo.max < head.value))
				&& (rightInfo == null ? true : (rightInfo.maxSubBSTHead == head.right && rightInfo.min > head.value))) {
			maxSubBSTHead = head;
			maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
					+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
		}
		return new Info(maxSubBSTHead, maxSubBSTSize, min, max);
	}

例7:给定一棵二叉树的头节点head,返回这颗二叉树中是不是完全二叉树

完全二叉树

基本解法是做宽度优先遍历

1、任何节点有右,无左,一定不是完全二叉树,否则继续

2、一旦遇到,左右不双全,后续结点都必须是叶节点

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

递归套路:

以X为头的二叉树是不是满二叉树,根据最后一个缺口的位置来分

1、没有缺口,是一个满二叉树

2、有缺口

(1)、缺口(左树的叶子几点不是满的)在左树上

(2)、左树满了,但是没到右树

(3)、左树满了,右树上挂了节点

需要的信息,是否满,高度

如果左,右都满,高度一样,那么符合条件1

左树是否是完全二叉树,右树是满,左树高度比右树高度大1,符合条件2.1

左树是满的,右树是满的,左树的高度比右树高度大1,符合条件2.2

左树是满的,右树是完全二叉树,左树与右树高度一样,符合条件2.3

 

              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 boolean isCBT2(Node head) {
		if (head == null) {
			return true;
		}
		return process(head).isCBT;
	}

        public static Info process(Node head) {
		if (head == null) {
			return new Info(true, true, 0);
		}
		Info leftInfo = process(head.left);
		Info rightInfo = process(head.right);
		int height = Math.max(leftInfo.height, rightInfo.height) + 1;
		boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
		boolean isCBT = false;
		if (isFull) {
			isCBT = true;
		} else {
			// 以X为头,不满,左树与右树有一个不是完全,那么条件2.1,2.2,2.3都不成立
			if (leftInfo.isCBT && rightInfo.isCBT) {
				// 2.1
				if (leftInfo.isCBT && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
					isCBT = true;
				}
				// 长满了 2.2
				if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
					isCBT = true;
				}
				// 最后一层结点左边撑满了,条件2.3
				if (leftInfo.isFull && rightInfo.isCBT && leftInfo.height == rightInfo.height) {
					isCBT = true;
				}
			}
		}
		return new Info(isFull, isCBT, height);
	}

例8:给定一棵二叉树的头节点head,和另外两个节点a和b。返回a和b的最低公共祖先

二叉树上两个节点的最低公共祖先

公共祖先变形1

公共祖先变形2

公共祖先变形3

解法一:通过遍历实现父节点表,将a的所有父节点行程一个HashSet,然后找b的祖先,有交汇就是最低公共祖先

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

        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、a,b 无一个在x上

2、a,b只有一个在x上

3、a,b都在x上

3.1 左右各有一个

3.2 a,b都在左

3.3 a,b都在右

3.4 x是a,或b

 

        public static class Info {
		// 最初交汇点
		public Node ans;
		// 发现了o1
		public boolean findO1;
		// 发现了o2
		public boolean findO2;

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



        public static Node lowestAncestor2(Node head, Node o1, Node o2) {
		return process(head, o1, o2).ans;
	}

        public static Info process(Node head, Node o1, Node o2) {
		if (head == null) {
			return new Info(null, false, false);
		}
		Info leftInfo = process(head.left, o1, o2);
		Info rightInfo = process(head.right, o1, o2);

		boolean findO1 = head == o1 || leftInfo.findO1 || rightInfo.findO1;
		boolean findO2 = head == o2 || leftInfo.findO2 || rightInfo.findO2;
		Node ans = null;
		// 左树上已经提前交汇了
		if (leftInfo.ans != null) {
			ans = leftInfo.ans;
		}
		// 右树已经提前交汇了
		if (rightInfo.ans != null) {
			ans = rightInfo.ans;
		}
		// 左,右都没提前交汇,但是此时又都找到了,那么说明X就是交汇点
		if (ans == null) {
			if (findO1 && findO2) {
				ans = head;
			}
		}
		// 到了这一步,就说明至少有一个没找到,所以将信息传递上去,继续判断
		return new Info(ans, findO1, findO2);
	}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值