二叉搜索子树的最大键值和
- 问题描述
- 问题简单分析
- 提交之旅
- 第一次提交-失败
- 第二次提交-失败
- 第三次提交-成功
问题描述
二叉搜索子树的最大键值和
给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。
二叉搜索树的定义如下:
- 任意节点的左子树中的键值都 小于 此节点的键值。
- 任意节点的右子树中的键值都 大于 此节点的键值。
- 任意节点的左子树和右子树都是二叉搜索树。
示例 1:
输入:root = [1,4,3,2,4,2,5,null,null,null,null,null,null,4,6]
输出:20
解释:键值为 3 的子树是和最大的二叉搜索树。
问题简单分析
对于树的处理一般都采用递归的方式,再配以某种遍历(前序、中序、后序)方式。对于该问题也是类似的处理方式。
本问题本质上从一颗二叉树中查找节点之和最大的二叉搜索树。
判断某个节点的子树是否是二叉搜索树,需要先检查该节点的左右子树是否是二叉搜索树,再判断该节点是否是二叉搜索树,因此这里使用的是后序遍历的方式。当左子树或右子树任意一个不满足时,该子树的祖先节点都将不是二叉搜索树。
父节点需要能够区分左右子树是否是二叉搜索树。
基于以上考虑,就有了第一次提交。
提交之旅
第一次提交-失败
二叉搜索子树的区分:当返回值小于0,表示不是二叉搜索树
class Solution {
public:int maxSumBST(TreeNode* root) {int maxSum = 0;maxSumBST(root, maxSum);return maxSum;}int maxSumBST(TreeNode* root, int& maxSum) {//如果是空节点,则直接返回0,避免左右子树这里还要加个是否为空的处理逻辑if(root == nullptr){return 0;}int leftSum = maxSumBST(root->left, maxSum);int rightSum = maxSumBST(root->right, maxSum);//后序处理逻辑//当左右子树任意个小于0,则返回-1,表示不是二叉搜索树if(leftSum < 0 || rightSum < 0){return -1;}//左节点不小于父节点,则说明不是二叉搜索树,返回-1 ---- 这里存在问题if(root->left != nullptr && root->val <= root->left->val){return -1;}//右节点不大于父节点,则说明不是二叉搜索树,返回-1 ---- 这里存在问题if(root->right != nullptr && root->val >= root->right->val){return -1;}//统计二叉搜索树的节点之和int sum = root->val + leftSum + rightSum;if(maxSum < sum){maxSum = sum;}return sum;}};
提交后就失败了,失败用例:[1,null,10,-5,20],期望是25,而实际返回20。原因如下:
节点10的左子节点是一个叶子节点,但是由于其值为-5(小于0),被误判为非二叉搜索树,导致[10,-5,20] 这颗子树也被误判为非二叉搜索树,因此最终返回20。
因此必须想其他方案来区分是否是二叉搜索树,因此就有了第二次提交。
第二次提交-失败
二叉搜索子树的区分:通过tuple(是否是二叉搜索树,树节点之和,最小节点)中专门字段来区分,当为true,则表示是二叉搜索树
class Solution {
public:int maxSumBST(TreeNode* root) {int maxSum = 0;maxSumBST(root, maxSum);return maxSum;}tuple<bool,int,TreeNode*> maxSumBST(TreeNode* root, int& maxSum) {if(root == nullptr){return make_tuple(true, 0, nullptr);}tuple<bool,int,TreeNode*> left = maxSumBST(root->left, maxSum);tuple<bool,int,TreeNode*> right = maxSumBST(root->right, maxSum);if(!std::get<0>(left) || !std::get<0>(right)){return make_tuple(false, 0, nullptr);}//左子树的最小节点不小于父节点,则说明不是二叉搜索树,返回-1 ---- 这里存在问题if(std::get<2>(left) && root->val <= std::get<2>(left)->val) {return make_tuple(false, 0, nullptr);}//右子树的最小节点不大于父节点,则说明不是二叉搜索树,返回-1if(std::get<2>(right) && root->val >= std::get<2>(right)->val) {return make_tuple(false, 0, nullptr);}int sum = root->val + std::get<1>(left) + std::get<1>(right);if(maxSum < sum){maxSum = sum;}return make_tuple(true, sum, (root-> left ? root->left : root));}};
为啥tuple中会有 最小节点?这是因为用例[1,null,10,-5,20]期望是25,而实际返回26,这是因为原来的实现是通过判断节点跟其左右节点的关系来判断是否是二叉搜索树,这就导致误将[1,null,10,-5,20]也当作合法的二叉搜索树。
[10,-5,20]是合法的二叉搜索树,[1,null,10]也是合法的二叉搜索树,但是[1,null,10,-5,20]树中-5在1的右子树上,但小于1,所以这棵树不是合法的二叉搜索树。
既然右子树的最小节点不大于父节点,那么应该是左子树的最大节点不小于父节点,而不是左子树的最小节点不小于父节点?这是因为测试时不小心点了提交!
第三次提交-成功
二叉搜索树判定:父节点需要大于左子树的最大节点,小于右子树的最小节点。
class Solution {
public:int maxSumBST(TreeNode* root) {int maxSum = 0;maxSumBST(root, maxSum);return maxSum;}//返回:是否是二叉搜索树,树节点之和,最小节点,最大节点tuple<bool,int,TreeNode*,TreeNode*> maxSumBST(TreeNode* root, int& maxSum) {if(root == nullptr){return make_tuple(true, 0, nullptr, nullptr);}tuple<bool,int,TreeNode*,TreeNode*> left = maxSumBST(root->left, maxSum);tuple<bool,int,TreeNode*,TreeNode*> right = maxSumBST(root->right, maxSum);if(!std::get<0>(left) || !std::get<0>(right)){return make_tuple(false, 0, nullptr, nullptr);}//根节点需要大于左子树的最大值if(std::get<3>(left) && root->val <= std::get<3>(left)->val) {return make_tuple(false, 0, nullptr, nullptr);}//根节点需要小于右子树的最小值if(std::get<2>(right) && root->val >= std::get<2>(right)->val) {return make_tuple(false, 0, nullptr, nullptr);}int sum = root->val + std::get<1>(left) + std::get<1>(right);if(maxSum < sum){maxSum = sum;}//找到该节点子树的最大最小节点TreeNode* min = std::get<2>(left) ? std::get<2>(left) : root;TreeNode* max = std::get<3>(right) ? std::get<3>(right) : root;;return make_tuple(true, sum, min, max);}};
核心关键点:
- 后序遍历
- 二叉搜索树的判定:节点值大于左子树的最大值,小于右子树的最小值。