题目
与前一题不同,本题不是查找最大的子树而是拓扑结构,所以下面的解法都是采用由上到下的搜索得到最大的拓扑结构。本文给出一种和书上不同的优化方法,可以将时间复杂度降低到O(n)同时空间复杂度为O(h)
思路
思路一
如果要求最大的拓扑结构,那么最直接的办法就是对每一个节点进行一次搜索,计算以该节点为根节点的最大拓扑结构。所以现在问题是如何从根节点得到一个拓扑节结构。
在二叉搜索树中插入节点时,是通过查找一个能插入当前节点的空隙,这个空隙也就是一个范围。如果插入的节点是父节点的左节点,那么该节点的值应该要比父节点小,该节点肯定包含在某个最小右子树中,这个最小右子树的父节点(如果不为空)值一定比插入的节点小。插入的节点是右节点时类似。
因此可以模拟这种插入过程,递归判断左右节点是否在允许的范围内就可以得到一个拓扑结构。下面给出递归解法,搜索每个节点的拓扑结构,然后返回最大的拓扑结构。
代码
/*
* 遍历二叉树,对每一个节点,搜索以该节点为根的最大符合二叉
* 搜索树结构的拓扑结构。
* 时间复杂度为O(n^2),空间复杂度为O(h)
*/
public int getTopoSize1(TreeNode root) {
if (root == null)
return 0;
return Math.max(getTopoSizeOfNodeRecur(root,
Integer.MIN_VALUE, Integer.MAX_VALUE),
Math.max(getTopoSize1(root.left),
getTopoSize1(root.right)));
}
/*
* 递归搜索从根节点开始的最大符合二叉搜索结构的拓扑结构
* 因为每个节点都有一个范围,这个范围由已经遍历过的祖先节点决定,
* 如果节点在范围内,则应该是拓扑结构中的一个元素。
*/
public int getTopoSizeOfNodeRecur(TreeNode root, int min, int max) {
if (root == null || root.val < min || root.val > max)
return 0;
return 1 + getTopoSizeOfNodeRecur(root.left, min, root.val) +
getTopoSizeOfNodeRecur(root.right, root.val, max);
}
思路二
&esnp;书中给出了一种解法可以降低时间复杂度,但是空间复杂增加了,具体实现可以直接参考书上的解释。这里结合前面的解法进行优化,可以在不增加空间复杂度的情况下,将时间复杂度降低到O(n)。前面的解法中存在一部分冗余,这是由于拓扑结构有重合部分。例如,根节点的拓扑结构的左侧部分和根节点左子树的拓扑结构有一部分重合。通过观察发现,它们不重合的部分出现在左子树的右边界上,也就是说右边界上某个节点大于根节点时,那么这个节点以及其后裔都不属于根节点的拓扑结构,但是属于根节点的左节点的拓扑结构。当然,右边界上的节点也可能小于其父节点,这时,这个节点以及其后裔,既不属于根节点的拓扑结构也不属于根节点的左节点的拓扑结构。
为了消除冗余,可以通过下面的步骤不重复的搜索所有节点的拓扑结构
- 搜索根节点的拓扑结构
- 搜索左节点
- 搜索右节点
- 搜索左子树右边不属于根节点拓扑结构的部分
- 搜索右子树左边不属于根节点拓扑结构的部分
代码
public int getTopoSize2(TreeNode root) {
MAX_SEARCH_TREE_SIZE = 0;
getTopoSizeOfNodeRecur(root, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
return MAX_SEARCH_TREE_SIZE;
}
/*
* 最大拓扑结构有三种可能,
* 1、左子树中最大的拓扑结构
* 2、右子树中最大的拓扑结构
* 3、根节点的拓扑结构
* 为了求上面3种情况,需要递归求出所有节点的拓扑结构,然后得到其中的最大值。
* 由于拓扑结构有重合,会产生很多重复的搜索。
* 拓扑结构重合意思是根节点的左节点的拓扑结构和根节点的拓扑结构有一部分
* 重合。 通过观察可以发现根节点的左节点的拓扑结构和 根节点的拓扑结构不重合的
* 部分只能出现在左子树的右边界上,也就是从左子树的右边界的某个节点开始,
* 该节点以及其后裔不属于根节点的拓扑结构。
* 因此搜索出根节点的拓扑结构后,左节点的拓扑结构就得到了一部分,现在只需要
* 搜索剩下不重合的部分,就能得到左节点的完整拓扑结构。
* 根节点的有节点也有相同情况。
* 因此通过如下步骤就可以不重复的搜索所有节点的拓扑结构
* 1、搜索根节点的拓扑结构
* 2、搜索左子树最右边一部分子树(不包含在根节点拓扑结构中)
* 3、搜索右子树最左边一部分子树(不包含在根节点拓扑结构中)
*/
public int getTopoSizeOfNodeRecur(TreeNode root, int offset, int min, int max) {
if (root == null || root.val < min || root.val > max)
return 0;
int total = 0;
int leftCount = getTopoSizeOfNodeRecur(root.left, 0, min, root.val);
int rightCount = getTopoSizeOfNodeRecur(root.right, 0, root.val, max);
total = 1 + leftCount + rightCount + offset;
MAX_SEARCH_TREE_SIZE = Math.max(total, MAX_SEARCH_TREE_SIZE);
TreeNode p = root.left;
while (p != null && p.val >= min && p.val <= root.val) {
min = p.val;
p = p.right;
}
if (p != null) {
if (p.val < min) {
MAX_SEARCH_TREE_SIZE = Math.max(MAX_SEARCH_TREE_SIZE,
getTopoSizeOfNodeRecur(p, 0, Integer.MIN_VALUE, Integer.MAX_VALUE));
} else {
MAX_SEARCH_TREE_SIZE = Math.max(MAX_SEARCH_TREE_SIZE,
getTopoSizeOfNodeRecur(p, leftCount, min, Integer.MAX_VALUE));
}
}
p = root.right;
while (p != null && p.val >= root.val && p.val <= max) {
max = p.val;
p = p.left;
}
if (p != null) {
if (p.val > max)
MAX_SEARCH_TREE_SIZE = Math.max(MAX_SEARCH_TREE_SIZE,
getTopoSizeOfNodeRecur(p, 0, Integer.MIN_VALUE, Integer.MAX_VALUE));
else
MAX_SEARCH_TREE_SIZE = Math.max(MAX_SEARCH_TREE_SIZE,
getTopoSizeOfNodeRecur(p, rightCount, Integer.MIN_VALUE, max));
}
return total;
}