第三章:二叉树问题(五)

判断t1树是否包含t2树全部的拓扑结构

【题目】

给定彼此独立的两棵树头节点分别为t1和t2,判断t1树是否包含t2树全部的拓扑结构。

例如,如图3-34所示的t1树和如图3-35所示的t2树。


t1树包含t2树全部的拓扑结构,所以返回true。

【难度】

士     ★☆☆☆

【解答】

如果t1中某棵子树头节点的值与t2头节点的值一样,则从这两个头节点开始匹配,匹配的每一步都让t1上的节点跟着t2上的节点的先序遍历移动,每移动一步,都检查t1的当前节点是否与t2当前节点的值一样。比如,题目中的例子,t1中的节点2与t2中的节点2匹配,然后t1跟着t2向左,发现t1中的节点4与t2中的节点4匹配,t1跟着t2继续向左,发现t1中的节点8与t2中的节点8匹配,此时t2回到t2中的节点2,t1也回到t1中的节点2,然后t1跟着t2向右,发现t1中的节点5与t2中的节点5匹配。t2匹配完毕,结果返回true。如果匹配的过程中发现有不匹配的情况,则直接返回false,说明t1的当前子树从头节点开始,无法与t2匹配,那么再去寻找t1的下一棵子树。t1的每棵子树上都有可能匹配出t2,所以都要检查一遍。

因此,如果t1的节点数为N,t2的节点数为M,则该方法的时间复杂度为O(N´M)。

具体过程请参看如下代码中的contains方法,

 

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

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

	public static boolean contains(Node t1, Node t2) {
		if (t2 == null) {
			return true;
		}
		if (t1 == null) {
			return false;
		}
		return check(t1, t2) || contains(t1.left, t2) || contains(t1.right, t2);
	}

	public boolean check(Node h, Node t2) {
		if (t2 == null) {
			return true;
		}
		if (h == null || h.value != t2.value) {
			return false;
		}
		return check(h.left, t2.left) && check(h.right, t2.right);
	}

判断t1树中是否有与t2树拓扑结构完全相同的子树

【题目】

给定彼此独立的两棵树头节点分别为t1和t2,判断t1中是否有与t2树拓扑结构完全相同的子树。

例如,如图3-36所示的t1树和如图3-37所示的t2树。


t1树有与t2树拓扑结构完全相同的子树,所以返回true。但如果t1树和t2树分别如图3-38和图3-39所示,则t1树就没有与t2树拓扑结构完全相同的子树,所以返回false。


 【难度】

校     ★★★☆

【解答】

如果t1的节点数为N,t2的节点数为M,则本题最优解是时间复杂度为O(N+M)的方法。首先简单介绍一个时间复杂度为O(N´M)的方法,对于t1的每棵子树,都去判断是否与t2树的拓扑结构完全一样,这个过程的时间复杂度为O(M),t1的子树一共有N棵,所以时间复杂度为O(N´M),这种方法在本书中不再详述。

下面重点介绍一下时间复杂度为O(N+M)的方法,首先把t1树和t2树按照先序遍历的方式序列化,关于这个内容,请阅读本书“二叉树的序列化和反序列化”问题。以题目的例子来说,t1树序列化后的结果为“1!2!4!#!8!#!#!5!9!#!#!#!3!6!#!#!7!#!#!”,记为t1Str。t2树序列化后的结果为“2!4!#!8!#!#!5!9!#!#!#!”,记为t2Str。接下来,只要验证t2Str是否是t1Str的子串即可,这个用KMP算法可以在线性时间内解决。因此,t1序列化的过程为O(N),t2序列化的过程为O(M),KMP解决t1Str和t2Str的匹配问题O(M+N),所以时间复杂度为O(M+N)。有关KMP算法的内容,请读者阅读本书“KMP算法”问题,关于这个算法非常清晰的解释,这里不再详述。

本题最优解的全部过程请参看如下代码中的isSubtree方法。

 

	public boolean isSubtree(Node t1, Node t2) {
		String t1Str = serialByPre(t1);
		String t2Str = serialByPre(t2);
		return getIndexOf(t1Str, t2Str) != -1;
	}

	public String serialByPre(Node head) {
		if (head == null) {
			return "#!";
		}
		String res = head.value + "!";
		res += serialByPre(head.left);
		res += serialByPre(head.right);
		return res;
	}

	// KMP
	public int getIndexOf(String s, String m) {
		if (s == null || m == null || m.length() < 1 || s.length() < m.length()) 		{
			return -1;
		}
		char[] ss = s.toCharArray();
		char[] ms = m.toCharArray();
		int si = 0;
		int mi = 0;
		int[] next = getNextArray(ms);
		while (si < ss.length && mi < ms.length) {
			if (ss[si] == ms[mi]) {
				si++;
				mi++;
			} else if (next[mi] == -1) {
				si++;
			} else {
				mi = next[mi];
			}
		}
		return mi == ms.length ? si - mi : -1;
	}

	public int[] getNextArray(char[] ms) {
		if (ms.length == 1) {
			return new int[] { -1 };
		}
		int[] next = new int[ms.length];
		next[0] = -1;
		next[1] = 0;
		int pos = 2;
		int cn = 0;
		while (pos < next.length) {
			if (ms[pos - 1] == ms[cn]) {
				next[pos++] = ++cn;
			} else if (cn > 0) {
				cn = next[cn];
			} else {
				next[pos++] = 0;
			}
		}
		return next;
	}

判断二叉树是否为平衡二叉树

【题目】

平衡二叉树的性质为:要么是一棵空树,要么任何一个节点的左右子树高度差的绝对值不超过1。给定一棵二叉树的头节点head,判断这棵二叉树是否为平衡二叉树。

【要求】

如果二叉树的节点数为N,则要求时间复杂度为O(N)。

【难度】

士     ★☆☆☆

【解答】

平衡二叉树的标准是:对任何子树来说,左子树和右子树的高度差都不超过1。本题解法的整体过程为树形dp套路,请读者先阅读本书“找到二叉树中的最大搜索二叉子树”问题了解这个套路,本题是这个套路的再次展示。

首先,树形dp套路的前提是满足的。依次考查每个节点为头节点的子树,如果都是平衡二叉树,那么整体就是平衡二叉树。

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

可能性一:如果X的左子树不是平衡的,则以X为头节点的树就是不平衡的。

可能性二:如果X的右子树不是平衡的,则以X为头节点的树就是不平衡的。

可能性三:如果X的左子树和右子树高度差超过1,则以X为头节点的树就是不平衡的。

可能性四:如果上面可能性都没中,那么以X为头节点的树是平衡的。

树形dp套路第二步:根据第一步的可能性分析,列出所有需要的信息。左子树和右子树都需要知道各自是否平衡,以及高度这两个信息。

树形dp套路第三步:根据第二步信息汇总。定义信息如ReturnType类所示。

	public class ReturnType {
		public boolean isBalanced;
		public int height;

		public ReturnType(boolean isBalanced, int height) {
			this.isBalanced = isBalanced;
			this.height = height;
		}
	}

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

	public ReturnType process(Node head) {
		if (head == null) {
			return new ReturnType(true, 0);
		}
		ReturnType leftData = process(head.left);
		ReturnType rightData = process(head.right);
		int height = Math.max(leftData.height, rightData.height) + 1;
		boolean isBalanced = leftData.isBalanced && rightData.isBalanced
				&& Math.abs(leftData.height - rightData.height) < 2;
		return new ReturnType(isBalanced, height);
	}

	public boolean isBalanced(Node head) {
		return process(head).isBalanced;
	}


根据后序数组重建搜索二叉树

【题目】

给定一个整型数组arr,已知其中没有重复值,判断arr是否可能是节点值类型为整型的搜索二叉树后序遍历的结果。

进阶问题:如果整型数组arr中没有重复值,且已知是一棵搜索二叉树的后序遍历结果,通过数组arr重构二叉树。

【难度】

士     ★☆☆☆

【解答】

原问题的解法。二叉树的后序遍历为先左、再右、最后根的顺序,所以,如果一个数组是二叉树后序遍历的结果,那么头节点的值一定会是数组的最后一个元素。根据搜索二叉树的性质,比后序数组最后一个元素值小的数组会在数组的左边,比数组最后一个元素值大的数组会在数组的右边。比如,arr=[2,1,3,6,5,7,4],比4小的部分为[2,1,3],比4大的部分为[6,5,7]。如果不满足这种情况,则说明这个数组一定不可能是搜索二叉树后序遍历的结果。接下来,数组划分成左边数组和右边数组,相当于二叉树分出了左子树和右子树,只要递归地进行如上判断即可。

具体过程请参看如下代码中的isPostArray方法。

 

	public boolean isPostArray(int[] arr) {
		if (arr == null || arr.length == 0) {
			return false;
		}
		return isPost(arr, 0, arr.length - 1);
	}

	public boolean isPost(int[] arr, int start, int end) {
		if (start == end) {
			return true;
		}
		int less = -1;
		int more = end;
		for (int i = start; i < end; i++) {
			if (arr[end] > arr[i]) {
				less = i;
			} else {
				more = more == end ? i : more;
			}
		}
		if (less == -1 || more == end) {
			return isPost(arr, start, end - 1);
		}
		if (less != more - 1) {
			return false;
		}
		return isPost(arr, start, less) && isPost(arr, more, end - 1);
	}

进阶问题的分析与原问题同理,一棵树的后序数组中最后一个值为二叉树头节点的值,数组左部分都比头节点的值小,用来生成头节点的左子树,剩下的部分用来生成右子树。

具体过程请参看如下代码中的posArrayToBST方法。

 

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

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

	public Node posArrayToBST(int[] posArr) {
		if (posArr == null) {
			return null;
		}
		return posToBST(posArr, 0, posArr.length - 1);
	}

	public Node posToBST(int[] posArr, int start, int end) {
		if (start > end) {
			return null;
		}
		Node head = new Node(posArr[end]);
		int less = -1;
		int more = end;
		for (int i = start; i < end; i++) {
			if (posArr[end] > posArr[i]) {
				less = i;
			} else {
				more = more == end ? i : more;
			}
		}
		head.left = posToBST(posArr, start, less);
		head.right = posToBST(posArr, more, end - 1);
		return head;
	}


判断一棵二叉树是否为搜索二叉树和完全二叉树

【题目】

给定二叉树的一个头节点head,已知其中没有重复值的节点,实现两个函数分别判断这棵二叉树是否为搜索二叉树和完全二叉树。

【难度】

士     ★☆☆☆

【解答】

判断一棵二叉树是否为搜索二叉树,只要改写一个二叉树中序遍历,在遍历的过程中看节点值是否都是递增的即可。本书改写的是Morris中序遍历,所以时间复杂度为O(N),额外空间复杂度为O(1)。有关Morris中序遍历的介绍,请读者阅读本书“遍历二叉树的神级方法”问题。需要注意的是,Morris遍历分调整二叉树结构和恢复二叉树结构两个阶段。因此,当发现节点值是降序时,不能直接返回false,这么做可能会跳过恢复阶段,从而破坏二叉树的结构。

通过改写Morris中序遍历来判断搜索二叉树的过程请参看如下代码中的isBST方法。

 

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

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

	public boolean isBST(Node head) {
		if (head == null) {
			return true;
		}
		boolean res = true;
		Node pre = null;
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
				}
			}
			if (pre != null && pre.value > cur1.value) {
				res = false;
			}
			pre = cur1;
			cur1 = cur1.right;
		}
		return res;
	}

判断一棵二叉树是否为完全二叉树,依据以下标准会使判断过程变得简单且易实现。

1.按层遍历二叉树,从每层的左边向右边依次遍历所有的节点。

2.如果当前节点有右孩子节点,但没有左孩子节点,则直接返回false。

3.如果当前节点并不是左右孩子节点全有,那么之后的节点必须都为叶节点,否则返回false。

4.遍历过程中如果不返回false,则遍历结束后返回true。

判断是否是完全二叉树的全部过程请参看如下代码中的isCBT方法。

 

	public boolean isCBT(Node head) {
		if (head == null) {
			return true;
		}
		Queue<Node> queue = new LinkedList<Node>();
		boolean leaf = false;
		Node l = null;
		Node r = null;
		queue.offer(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.offer(l);
			}
			if (r != null) {
				queue.offer(r);
			} else {
				leaf = true;
			}
		}
		return true;
	}

通过有序数组生成平衡搜索二叉树

【题目】

给定一个有序数组sortArr,已知其中没有重复值,用这个有序数组生成一棵平衡搜索二叉树,并且该搜索二叉树中序遍历的结果与sortArr一致。

【难度】

士     ★☆☆☆

【解答】

本题的递归过程比较简单,用有序数组中最中间的数生成搜索二叉树的头节点,然后用这个数左边的数生成左子树,用右边的数生成右子树即可。

全部过程请参看如下代码中的generateTree方法。

 

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

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

	public Node generateTree(int[] sortArr) {
		if (sortArr == null) {
			return null;
		}
		return generate(sortArr, 0, sortArr.length - 1);
	}

	public Node generate(int[] sortArr, int start, int end) {
		if (start > end) {
			return null;
		}
		int mid = (start + end) / 2;
		Node head = new Node(sortArr[mid]);
		head.left = generate(sortArr, start, mid - 1);
		head.right = generate(sortArr, mid + 1, end);
		return head;
	}

程序员代码面试指南 文章被收录于专栏

<p> 本专刊为左程云书《程序员代码面试指南》前三章,已获得官方授权,想看书中更多内容可去购买书籍查看。 同时点击可查看牛客算法笔面试真题精讲班,每周由左老师讲解经典高频校招题目:https://www.nowcoder.com/courses/cover/live/350 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>

全部评论

相关推荐

11-02 08:15
已编辑
门头沟学院 Java
美团 Java后端开发 10w刀 美硕
YamadaAnna:包留美的,你拿的美团 招银,没一个不加班的。考虑一下未来吧,应届生的工资真不重要,10w刀税后6w,省省还是能活下去的。回国了35岁怎么办,难道35岁还能返美么,就算35岁还能在国内找到工作,难道打算一辈子9点10点下班么。你有能力在美利坚找到工作,回国如果不是哪个965大厂给你发个ssp,真不值得。 等抽不中h1b,没办法了再回国吧。
点赞 评论 收藏
分享
牛客339922477号:都不用reverse,直接-1。一行。啥送分题
点赞 评论 收藏
分享
头像
10-16 09:58
已编辑
门头沟学院 Java
点赞 评论 收藏
分享
1 1 评论
分享
牛客网
牛客企业服务