Range Sum of BST
EasyPrompt
Given the root node of a binary search tree and two integers low and high, return the sum of values of all nodes with a value in the inclusive range [low, high].
Example 1:
Input: root = [10,5,15,3,7,null,18], low = 7, high = 15
Output: 32
Explanation: Nodes 7, 10, and 15 are in the range [7, 15]. 7 + 10 + 15 = 32.Example 2:
Input: root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
Output: 23
Explanation: Nodes 6, 7, and 10 are in the range [6, 10]. 6 + 7 + 10 = 23.
Constraints:
- The number of nodes in the tree is in the range
[1, 2 * 104]. 1 <= Node.val <= 1051 <= low <= high <= 105- All
Node.valare unique.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves traversing the entire binary tree, node by node, without taking advantage of the Binary Search Tree (BST) properties. For each visited node, we check if its value falls within the given range [low, high]. If it does, we add its value to a running total. Any standard tree traversal algorithm like Depth-First Search (DFS) or Breadth-First Search (BFS) can be used.
Algorithm
- Initialize a global or instance variable
sumto 0. - Define a recursive function
dfs(node, low, high). - Base Case: If the
nodeis null, simply return. - Processing: Check if the current
node.valis within the inclusive range[low, high]. - If
node.val >= low && node.val <= high, addnode.valto thesum. - Recursive Step: Unconditionally make recursive calls for both the left and right children:
dfs(node.left, low, high)anddfs(node.right, low, high). - Start the process by calling
dfs(root, low, high)from the main function. - Return the final
sum.
Walkthrough
We can implement this using a recursive Depth-First Search (DFS).
We define a helper function that takes a node as input. The base case for the recursion is when the node is null. In the recursive step, we first check if the current node's value is within the inclusive range [low, high]. If it is, we add the value to our sum. Crucially, we then unconditionally make recursive calls for the left and right children of the current node, ensuring every node is visited.
This process continues until all nodes have been visited. The final sum is then returned.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { int sum = 0; public int rangeSumBST(TreeNode root, int low, int high) { dfs(root, low, high); return sum; } private void dfs(TreeNode node, int low, int high) { if (node == null) { return; } if (node.val >= low && node.val <= high) { sum += node.val; } // Unconditionally traverse left and right subtrees dfs(node.left, low, high); dfs(node.right, low, high); }}Complexity
Time
O(N), where N is the total number of nodes in the tree. This is because we must visit every single node to check its value.
Space
O(H), where H is the height of the tree. This space is used by the recursion stack. In the worst-case scenario of a skewed tree, H can be equal to N, making the space complexity O(N). For a balanced tree, it would be O(log N).
Trade-offs
Pros
Very simple to understand and implement.
It is a general solution that works for any binary tree, not just a BST.
Cons
Highly inefficient as it fails to use the ordering property of the BST.
It explores subtrees that cannot possibly contain values in the desired range, performing unnecessary work.
This approach leverages the fundamental property of a Binary Search Tree (BST) to significantly improve efficiency. The property states that for any given node, all values in its left subtree are smaller, and all values in its right subtree are larger. By using this property, we can "prune" entire subtrees from our search if we know they cannot contain values within the [low, high] range.
Algorithm
Recursive Approach:
- If the current
nodeis null, return 0. - If
node.val < low, it means all values in the left subtree are also less thanlow. So, we only need to search in the right subtree. ReturnrangeSumBST(node.right, low, high). - If
node.val > high, it means all values in the right subtree are also greater thanhigh. So, we only need to search in the left subtree. ReturnrangeSumBST(node.left, low, high). - If
low <= node.val <= high, the current node is in the range. Its value must be added to the sum. We still need to check both left and right subtrees for other valid nodes. Returnnode.val + rangeSumBST(node.left, low, high) + rangeSumBST(node.right, low, high).
Iterative Approach (DFS with Stack):
- Initialize
sum = 0and astack. Push therootnode. - Loop while the
stackis not empty. - Pop a
nodefrom the stack. - If the
nodeis not null: - If
node.valis within[low, high], addnode.valtosum. - If
node.val > low, push the left child onto the stack (as it might be in range). - If
node.val < high, push the right child onto the stack (as it might be in range). - After the loop, return
sum.
Walkthrough
We traverse the tree, but at each node, we make an intelligent decision about where to go next based on the BST property.
- If the current node's value is less than
low, we know that the current node and its entire left subtree are too small. Therefore, we only need to explore the right subtree. - If the current node's value is greater than
high, we know that the current node and its entire right subtree are too large. Therefore, we only need to explore the left subtree. - If the current node's value is within the range
[low, high], we add its value to our sum. Since values in both the left and right subtrees could potentially also be in the range, we must explore both subtrees.
This pruning strategy avoids visiting nodes that are guaranteed to be outside the range, making the traversal much faster on average. This can be implemented both recursively and iteratively.
Recursive Implementation
The recursive function returns the sum of the valid nodes in the subtree rooted at the current node.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public int rangeSumBST(TreeNode root, int low, int high) { if (root == null) { return 0; } // If current node's value is too high, search only in the left subtree if (root.val > high) { return rangeSumBST(root.left, low, high); } // If current node's value is too low, search only in the right subtree if (root.val < low) { return rangeSumBST(root.right, low, high); } // If current node's value is in range, add it to the sum // and explore both subtrees for other possible values in range. return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high); }}Iterative Implementation (using a Stack for DFS)
This version avoids recursion, which can be beneficial for extremely deep trees to prevent stack overflow.
class Solution { public int rangeSumBST(TreeNode root, int low, int high) { int sum = 0; if (root == null) { return sum; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); if (node != null) { // If node is in range, add its value if (node.val >= low && node.val <= high) { sum += node.val; } // If current node's value is greater than low, its left child might be in range if (node.val > low) { stack.push(node.left); } // If current node's value is less than high, its right child might be in range if (node.val < high) { stack.push(node.right); } } } return sum; }}Complexity
Time
O(N) in the worst case, where N is the number of nodes. This happens when the range `[low, high]` includes all nodes in the tree. However, on average, the performance is much better. The number of visited nodes is proportional to the height of the tree plus the number of nodes whose values are in the range, i.e., O(H + K), where H is the tree height and K is the number of nodes in the range.
Space
O(H), where H is the height of the tree. For the recursive approach, this is the depth of the recursion stack. For the iterative approach, this is the maximum size of the stack. In the worst case of a skewed tree, H can be N, leading to O(N) space. For a balanced tree, it's O(log N).
Trade-offs
Pros
Significantly more efficient on average than the brute-force approach.
Effectively utilizes the core properties of a BST to prune the search space.
The solution is clean and logical.
Cons
The recursive version could theoretically cause a stack overflow on an extremely deep, unbalanced tree, although this is unlikely given the problem constraints.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * public int val; * public TreeNode left; * public TreeNode right; * public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) { * this.val = val; * this.left = left; * this.right = right; * } * } */ public class Solution { public int RangeSumBST ( TreeNode root , int low , int high ) { return dfs ( root , low , high ); } private int dfs ( TreeNode root , int low , int high ) { if ( root == null ) { return 0 ; } int x = root . val ; int ans = low <= x && x <= high ? x : 0 ; if ( x > low ) { ans += dfs ( root . left , low , high ); } if ( x < high ) { ans += dfs ( root . right , low , high ); } return ans ; } }/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public int rangeSumBST ( TreeNode root , int low , int high ) { if ( root == null ) { return 0 ; } if ( low <= root . val && root . val <= high ) { return root . val + rangeSumBST ( root . left , low , high ) + rangeSumBST ( root . right , low , high ); } else if ( root . val < low ) { return rangeSumBST ( root . right , low , high ); } else { return rangeSumBST ( root . left , low , high ); } } }/** * 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: int rangeSumBST ( TreeNode * root , int low , int high ) { if ( root == nullptr ) return 0 ; if ( low <= root -> val && root -> val <= high ) { return root -> val + rangeSumBST ( root -> left , low , high ) + rangeSumBST ( root -> right , low , high ); } else if ( root -> val < low ) { return rangeSumBST ( root -> right , low , high ); } else { return rangeSumBST ( root -> left , low , high ); } } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def rangeSumBST ( self , root : TreeNode , low : int , high : int ) -> int : def search ( node ): if not node : return if low <= node . val <= high : self . ans += node . val search ( node . left ) search ( node . right ) elif node . val < low : search ( node . right ) elif node . val > high : search ( node . left ) self . ans = 0 search ( root ) return self . ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.