题目描述:
给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。
二叉搜索树的定义如下:
- 任意节点的左子树中的键值都 小于 此节点的键值。
- 任意节点的右子树中的键值都 大于 此节点的键值。
- 任意节点的左子树和右子树都是二叉搜索树。
示例:
算法思想:
首先,二叉树题目核心–明确当前节点要做什么!
假如此时站在“1”这个节点,首先要判断它是不是一棵BST,如果它不是BST,直接就不用考虑计算键值和的事情了。
怎么知道它是不是BST呢?
- 先看它的两个孩子是不是BST,如果有个娃不是BST,那么它一定也不是BST
- 再看把“1”这个节点加入以后,整个树是不是BST("1"应该比它的左子树的最大节点还大,并且比右子树的最小节点还小)
知道它是BST后,我们再计算整个树的键值。
这样的一个思路,是不是和后序遍历一模一样呢,都是先遍历左右子节点,然后再处理根节点,也就确定了大致代码框架。
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
//如果左右是BST
//如果根节点加入还是BST
//计算键值
}
这里的难点在于判断左右是否BST,如果使用递归运算,那么时间复杂度将会非常的高,这里会存在大量的重复子问题,我们可以使用动态规划的备忘录剪枝,同时记录下一棵树的状态(是否BST),最大最小值,所有树的键值和。
使用什么数据结构能同时记录这些呢?当然是数组了,这里我们定义一个包含四个元素的数组,分别表示该树的状态(是否是BST),该树的最小值,该树的最大值,以及该树所有键值和,时间复杂度一下子就变为O(n),只需遍历一遍树就能得到解了。
代码实现:
int maxSum = 0;
public int maxSumBST(TreeNode root) {
traverse(root);
return maxSum;
}
//返回一个大小为4的数组 int[]{isBST, min, max, sum}
int[] traverse(TreeNode root) {
//如果是空节点
if(root == null) {
return new int[]{1, Integer.MAX_VALUE, Integer.MIN_VALUE, 0};
}
//左右子树遍历
int[] left = traverse(root.left);
int[] right = traverse(root.right);
//对根节点的处理
int[] res = new int[4];
if(left[0] == 1 && right[0] == 1 && root.val > left[2] && root.val < right[1]){
//如果以root为起点的树符合BST定义
res[0] = 1;
res[1] = Math.min(left[1], root.val);
res[2] = Math.max(right[2], root.val);
res[3] = left[3] + right[3] + root.val;
maxSum = Math.maxSum(maxSum, res[3]);
} else {
//如果不符合BST定义,只需要设置一个标志位就可以了
res[0] == 0;
}
return res;
}