Start learning free

LESSON 306 OF 347 · BINARY TREES

Max Sum BST in C#: O(n) Post-Order DFS

Practice O(n) Read the full explanation

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].
STEP 1 OF 13
    Ctrl + Enter to check

    C# · BINARY TREES

    Max Sum Bst in C#, explained

    Problem overview

    The method MaxSumBST receives the root of a binary tree and returns the largest sum of keys over all subtrees that are binary search trees. A BST requires every key in the left subtree to be strictly smaller than the node's key, every key in the right subtree to be strictly greater, and both subtrees to be BSTs. If every key is negative, the answer is 0, for the empty BST.

    For [1, 4, 3, 2, 4, 2, 5, null, null, null, null, null, null, 4, 6] the best subtree is the one rooted at 3. Its left child is 2, its right child is 5, and 5 has children 4 and 6. That is a BST with sum 3 + 2 + 5 + 4 + 6 = 20. The root 1 has a left child 4, which is bigger than 1, so the whole tree is not a BST.

    Approach: each subtree reports four facts

    The other lesson on this problem tests every subtree from scratch, walking it again for the BST check and again for the sum. In the worst case that costs O(n²). This lesson visits each node once, after its children. That is post-order, and it works because a parent can decide using only a few facts about its children, so nothing has to be walked again.

    For each subtree, Dfs returns four numbers:

    • whether the subtree is a BST;
    • its minimum key;
    • its maximum key;
    • the sum of its keys.

    With these, a node is the root of a BST exactly when both child subtrees are BSTs, root.val is greater than the left maximum, and root.val is smaller than the right minimum. Each condition is one comparison. The sum of a node's subtree is its key plus the two child sums, and the minimum and maximum are updated from the children in constant time.

    An empty subtree needs values that never get in the way. It is a valid BST, with sum 0, and its minimum is int.MaxValue and its maximum is int.MinValue. A real parent key is always less than the "minimum" of an empty right side and greater than the "maximum" of an empty left side, so the comparisons pass without special cases.

    Step by step

    1. Keep the best sum in a field, int result, with the initializer = 0. The field initializer runs when the object is created, so no constructor is needed.
    2. Run the search and return the field. MaxSumBST calls Dfs(root), ignores the array it returns, and returns result.
    3. Declare Dfs to take a TreeNode and return an int[] of four entries: the BST flag, the minimum, the maximum and the sum. An int array can't hold a bool, so the flag is stored as 1 or 0.
    4. Empty subtree. Return {1, int.MaxValue, int.MinValue, 0}.
    5. Left report. Call Dfs(root.left) and unpack it into isLeftBST (the flag compared with 1), leftMin, leftMax and leftSum.
    6. Right report. Do the same for the right child.
    7. Start with the children's verdict. isRootBST is true only if both isLeftBST and isRightBST are.
    8. Sum. rootSum is root.val + leftSum + rightSum. It's needed even when the subtree is not a BST, because the parent receives it.
    9. Minimum. rootMin is the smallest of leftMin, rightMin and root.val, using two nested Math.Min calls.
    10. Maximum. rootMax is built the same way with Math.Max.
    11. Check the node's own key. If root.val <= leftMax or root.val >= rightMin, set isRootBST to false. The comparisons are strict, so equal keys also break the rule.
    12. Update the answer. If isRootBST is still true, set result to the larger of result and rootSum.
    13. Return the four facts in the order flag, minimum, maximum, sum. The ternary operator isRootBST ? 1 : 0 converts the flag.

    The solution in C#

    class Solution {
    
        int result = 0;
    
        public int[] Dfs(TreeNode root) {
            if (root == null) {
                return new int[]{1, int.MaxValue, int.MinValue, 0};
            }
    
            int[] leftSubtree = Dfs(root.left);
            bool isLeftBST = leftSubtree[0] == 1;
            int leftMin = leftSubtree[1];
            int leftMax = leftSubtree[2];
            int leftSum = leftSubtree[3];
    
            int[] rightSubtree = Dfs(root.right);
            bool isRightBST = rightSubtree[0] == 1;
            int rightMin = rightSubtree[1];
            int rightMax = rightSubtree[2];
            int rightSum = rightSubtree[3];
    
            bool isRootBST = isLeftBST && isRightBST;
            int rootSum = root.val + leftSum + rightSum;
            int rootMin = Math.Min(Math.Min(leftMin, rightMin), root.val);
            int rootMax = Math.Max(Math.Max(leftMax, rightMax), root.val);
    
            if (root.val <= leftMax || root.val >= rightMin) {
                isRootBST = false;
            }
            if (isRootBST) {
                result = Math.Max(result, rootSum);
            }
            return new int[]{isRootBST ? 1 : 0, rootMin, rootMax, rootSum};
        }
    
        public int MaxSumBST(TreeNode root) {
            Dfs(root);
            return result;
        }
    };
    

    C# details:

    • Field initializer. int result = 0; sets the starting value as part of the declaration. Because 0 is also the default for an int field, the initializer states the intent rather than changing anything.
    • Array literal. new int[]{1, int.MaxValue, int.MinValue, 0} creates and fills an array in one expression. The caller reads it by position, so the order has to be the same on both sides.
    • Bool to int and back. C# has no implicit conversion between bool and int. leftSubtree[0] == 1 produces a bool, and isRootBST ? 1 : 0 produces an int. A tuple such as (bool, int, int, int) would avoid this, but the lesson uses arrays.
    • Sentinels. int.MaxValue and int.MinValue are the largest and smallest int values. They are only compared, never added, so they can't overflow. Sums are added, but they only combine real keys: at most 40,000 keys with magnitude up to 40,000 gives at most 1.6 billion, within the int range of about 2.1 billion.
    • The search ignores the returned array at the top level. Calling a method and discarding its result is legal in C#, and the useful effect is the update of result.

    Complexity

    Time: O(n). Each node is visited once, and the work per node is a constant number of comparisons and one small array allocation.

    Space: O(h) for the recursion, where h is the height of the tree, up to n for a chain. The arrays of finished calls are discarded.

    Edge cases and common mistakes

    • All keys negative. The field starts at 0, so the answer is 0, the empty BST. [-4, -2, -5] gives 0.
    • Leaves. A leaf has two empty children. Their sentinels pass both comparisons, so every leaf is a BST with its own key as the sum.
    • Non-BST nodes still report values. Their minimum, maximum and sum are computed in any case. Only the flag is false, so ancestors fail the BST test but still know the correct sums.
    • Using < instead of <= in the check. That would accept a node equal to its left maximum or right minimum. The problem needs strict ordering.
    • Comparing with the direct children only. Using root.left.val would miss a large key deeper in the left subtree. Passing the minimum and maximum up avoids this.
    • Counting a non-BST sum. Updating result before the BST test is finished would accept subtrees that are not BSTs. The update comes after the key check.

    Testing

    • [2, 1, 3] gives 6.
    • [4, 3, null, 1, 2] gives 2.
    • [-4, -2, -5] gives 0.
    • [5, 4, 8, 3, null, 6, 3] gives 7.
    • [1, 4, 3, 2, 4, 2, 5, null, null, null, null, null, null, 4, 6] gives 20.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free