Maximum Sum BST in Binary Tree: II - C++ Solution and Time Complexity Analysis


Given a binary tree root, the task is to return the maximum sum of all keys of any sub-tree which is also a Binary Search Tree (BST).

Assume a BST is defined as follows:

  • The left subtree of a node contains only nodes with keys less than the node's key.
  • The right subtree of a node contains only nodes with keys greater than the node's key.
  • Both the left and right subtrees must also be binary search trees.

 

Example 1:

Input: root = [1,4,3,2,4,2,5,null,null,null,null,null,null,4,6]
Output: 20
Explanation: Maximum sum in a valid Binary search tree is obtained in root node with key equal to 3.

Example 2:

Input: root = [4,3,null,1,2]
Output: 2
Explanation: Maximum sum in a valid Binary search tree is obtained in a single root node with key equal to 2.

Example 3:

Input: root = [-4,-2,-5]
Output: 0
Explanation: All values are negatives. Return an empty BST.

Example 4:

Input: root = [2,1,3]
Output: 6

Example 5:

Input: root = [5,4,8,3,null,6,3]
Output: 7

 

Constraints:

  • Each tree has at most 40000 nodes..
  • Each node's value is between [-4 * 10^4 , 4 * 10^4].

Understanding the Problem

The core challenge of this problem is to identify the sub-trees within a binary tree that are valid Binary Search Trees (BSTs) and then calculate the sum of their nodes. The goal is to find the maximum sum among all such sub-trees.

This problem is significant in various applications such as database indexing, memory management, and more, where BST properties are leveraged for efficient data retrieval and storage.

Potential pitfalls include incorrectly identifying BSTs or missing edge cases where sub-trees might not be valid BSTs.

Approach

To solve this problem, we need to traverse the tree and check each sub-tree to see if it is a valid BST. A naive approach would involve checking every possible sub-tree, but this would be highly inefficient.

Instead, we can use a post-order traversal (left-right-root) to gather information about each sub-tree and determine if it is a BST. This allows us to efficiently calculate the sum of nodes for valid BSTs and keep track of the maximum sum encountered.

Naive Solution

The naive solution involves checking every possible sub-tree to see if it is a BST and then calculating the sum of its nodes. This approach is not optimal due to its high time complexity.

Optimized Solution

We can optimize the solution using a post-order traversal. During the traversal, we can gather information about the sub-trees, such as their minimum and maximum values, sum of nodes, and whether they are valid BSTs. This information can be used to determine if the current sub-tree is a valid BST and calculate its sum.

Algorithm

Here is a step-by-step breakdown of the optimized algorithm:

  1. Perform a post-order traversal of the tree.
  2. For each node, gather information about its left and right sub-trees (minimum value, maximum value, sum of nodes, and whether they are valid BSTs).
  3. Determine if the current node forms a valid BST with its left and right sub-trees.
  4. If it is a valid BST, calculate the sum of its nodes and update the maximum sum if necessary.
  5. Return the maximum sum encountered.

Code Implementation

#include <iostream>
#include <climits>
using namespace std;

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
public:
    struct SubTreeInfo {
        bool isBST;
        int sum;
        int minVal;
        int maxVal;
    };
    
    int maxSumBST(TreeNode* root) {
        int maxSum = 0;
        postOrderTraversal(root, maxSum);
        return maxSum;
    }
    
    SubTreeInfo postOrderTraversal(TreeNode* node, int &maxSum) {
        if (!node) {
            return {true, 0, INT_MAX, INT_MIN};
        }
        
        SubTreeInfo leftInfo = postOrderTraversal(node->left, maxSum);
        SubTreeInfo rightInfo = postOrderTraversal(node->right, maxSum);
        
        if (leftInfo.isBST && rightInfo.isBST && node->val > leftInfo.maxVal && node->val < rightInfo.minVal) {
            int currentSum = node->val + leftInfo.sum + rightInfo.sum;
            maxSum = max(maxSum, currentSum);
            return {true, currentSum, min(node->val, leftInfo.minVal), max(node->val, rightInfo.maxVal)};
        } else {
            return {false, 0, 0, 0};
        }
    }
};

// Example usage
int main() {
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(4);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(2);
    root->left->right = new TreeNode(4);
    root->right->left = new TreeNode(2);
    root->right->right = new TreeNode(5);
    root->right->right->left = new TreeNode(4);
    root->right->right->right = new TreeNode(6);
    
    Solution sol;
    cout << "Maximum Sum BST: " << sol.maxSumBST(root) << endl;
    
    return 0;
}

Complexity Analysis

The time complexity of the optimized solution is O(n), where n is the number of nodes in the tree. This is because we perform a single post-order traversal of the tree.

The space complexity is O(h), where h is the height of the tree. This is due to the recursive call stack used during the traversal.

Edge Cases

Potential edge cases include:

These edge cases are handled by the algorithm as it checks for null nodes and validates BST properties at each step.

Testing

To test the solution comprehensively, consider the following test cases:

Testing frameworks such as Google Test can be used to automate and validate the test cases.

Thinking and Problem-Solving Tips

When approaching such problems, consider the following tips:

Conclusion

In this blog post, we discussed the problem of finding the maximum sum of keys in any sub-tree of a binary tree that is also a BST. We explored a naive solution and an optimized solution using post-order traversal. We also provided a detailed C++ implementation and analyzed the complexity of the solution. By understanding and practicing such problems, you can improve your problem-solving skills and algorithmic thinking.

Additional Resources

For further reading and practice, consider the following resources: