Facebook Pixel

938. Range Sum of BST

Problem Description

You are given the root node of a binary search tree (BST) and two integers low and high. Your task is to find the sum of values of all nodes whose values fall within the inclusive range [low, high].

In other words, you need to traverse the BST and add up the values of all nodes where the node's value is greater than or equal to low and less than or equal to high.

The solution uses a depth-first search (DFS) approach that takes advantage of the BST property. Since it's a BST, for any node:

  • All values in the left subtree are smaller than the node's value
  • All values in the right subtree are larger than the node's value

The algorithm works as follows:

  1. If the current node is None, return 0

  2. Check if the current node's value x is within the range [low, high]. If yes, add it to the answer; otherwise, start with 0

  3. If x > low, there might be valid nodes in the left subtree (since left subtree has smaller values that could still be ≥ low), so recursively explore the left subtree

  4. If x < high, there might be valid nodes in the right subtree (since right subtree has larger values that could still be ≤ high), so recursively explore the right subtree

  5. Return the accumulated sum

This approach efficiently prunes branches that cannot contain values within the desired range, making it more efficient than traversing the entire tree.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary search tree is a special type of graph where each node has at most two children (left and right). Trees are a subset of graphs with no cycles and a hierarchical structure.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary search tree (BST). A BST is a tree data structure where each node follows the ordering property: left child values are less than the parent, and right child values are greater than the parent.

DFS

  • We arrive at DFS: Since we're dealing with a tree structure and need to traverse nodes to calculate the sum of values within a range, DFS is the appropriate approach.

Conclusion: The flowchart suggests using DFS (Depth-First Search) for this tree traversal problem. DFS is ideal here because:

  1. We need to visit nodes in the tree to check if their values fall within the given range [low, high]
  2. The BST property allows us to intelligently prune branches during traversal (if a node's value is less than low, we don't need to explore its left subtree for values in range)
  3. DFS naturally handles the recursive nature of tree traversal, making it straightforward to accumulate the sum as we visit valid nodes
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find the sum of node values within a range in a BST, the key insight is to leverage the BST's ordering property to make our search more efficient.

Think about how a BST is structured: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger. This property gives us valuable information about where to look for nodes within our target range [low, high].

Consider what happens when we visit a node with value x:

  1. If x is within [low, high], we include it in our sum. But we still need to check both subtrees because there might be valid values on both sides.

  2. If x < low, this node is too small. More importantly, since all values in the left subtree are even smaller than x, none of them can be within our range. We only need to check the right subtree where larger values exist.

  3. If x > high, this node is too large. Similarly, all values in the right subtree are even larger, so we can skip them entirely and only check the left subtree.

This leads us to a selective traversal strategy:

  • When x > low, explore the left subtree (because there might be values ≥ low there)
  • When x < high, explore the right subtree (because there might be values ≤ high there)

This is more efficient than blindly traversing the entire tree. We're essentially "pruning" branches that we know cannot contain values in our target range. The DFS approach naturally fits this recursive exploration pattern where we make decisions at each node about which subtrees are worth exploring.

The beauty of this solution is that it combines the BST property with conditional traversal to avoid unnecessary work, making the algorithm both elegant and efficient.

Learn more about Tree, Depth-First Search, Binary Search Tree and Binary Tree patterns.

Solution Approach

The implementation uses a recursive DFS function to traverse the BST and accumulate the sum of nodes within the range [low, high].

Let's walk through the implementation step by step:

1. Define the DFS Helper Function

We create a nested function dfs(root) that returns the sum of all node values in the subtree rooted at root that fall within the range [low, high].

2. Base Case

if root is None:
    return 0

When we reach a null node, we return 0 as there's nothing to add to our sum.

3. Check Current Node's Value

x = root.val
ans = x if low <= x <= high else 0

We extract the current node's value x and check if it's within the range. If low <= x <= high, we include it in our answer; otherwise, we start with 0.

4. Selective Traversal Based on BST Property

if x > low:
    ans += dfs(root.left)

If x > low, there might be nodes in the left subtree with values >= low (since left subtree contains smaller values than x). So we recursively explore the left subtree and add its result to our answer.

if x < high:
    ans += dfs(root.right)

If x < high, there might be nodes in the right subtree with values <= high (since right subtree contains larger values than x). So we recursively explore the right subtree and add its result to our answer.

5. Return the Result

return ans

We return the accumulated sum for the current subtree.

Key Optimization:

The clever part of this solution is the pruning logic:

  • When x <= low, we skip the left subtree entirely (all values there are < x <= low)
  • When x >= high, we skip the right subtree entirely (all values there are > x >= high)

This pruning significantly reduces the number of nodes visited compared to a naive approach that would traverse the entire tree.

Time Complexity: O(n) in the worst case where all nodes are within range, but typically better due to pruning.

Space Complexity: O(h) where h is the height of the tree, due to the recursion stack.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with a concrete example. Consider this BST with low = 7 and high = 15:

       10
      /  \
     5    15
    / \     \
   3   7    18

We want to find the sum of all nodes with values in the range [7, 15].

Step 1: Start at root (value = 10)

  • Check: Is 10 in [7, 15]? Yes! Add 10 to our sum. ans = 10
  • Since 10 > 7 (10 > low), explore left subtree
  • Since 10 < 15 (10 < high), explore right subtree

Step 2: Explore left subtree (root = 5)

  • Check: Is 5 in [7, 15]? No. ans = 0 for this node
  • Since 5 < 7 (5 < low), skip left subtree entirely (all values there are < 5 < 7)
  • Since 5 < 15 (5 < high), explore right subtree

Step 3: Visit node 7

  • Check: Is 7 in [7, 15]? Yes! Return 7
  • Since 7 = 7 (7 = low), skip left subtree
  • Since 7 < 15 (7 < high), explore right subtree (which is null, returns 0)
  • Return 7 to parent

Step 4: Back to node 5

  • Node 5 returns: 0 (its own value) + 7 (from right child) = 7

Step 5: Explore right subtree from root (node = 15)

  • Check: Is 15 in [7, 15]? Yes! ans = 15
  • Since 15 > 7 (15 > low), explore left subtree (which is null, returns 0)
  • Since 15 = 15 (15 = high), skip right subtree entirely (all values > 15)
  • Return 15

Step 6: Combine results at root

  • Root returns: 10 (its own value) + 7 (from left subtree) + 15 (from right subtree) = 32

Final Answer: 32

Notice how we intelligently pruned branches:

  • We never visited node 3 (skipped when at node 5 since 5 < low)
  • We never visited node 18 (skipped when at node 15 since 15 = high)

This pruning makes our algorithm more efficient than blindly traversing every node.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8class Solution:
9    def rangeSumBST(self, root: Optional[TreeNode], low: int, high: int) -> int:
10        """
11        Calculate the sum of all node values in a BST that fall within the range [low, high].
12      
13        Args:
14            root: The root node of the binary search tree
15            low: The lower bound of the range (inclusive)
16            high: The upper bound of the range (inclusive)
17      
18        Returns:
19            The sum of all node values within the specified range
20        """
21      
22        def dfs(node: Optional[TreeNode]) -> int:
23            """
24            Depth-first search to traverse the BST and sum values within range.
25          
26            Args:
27                node: Current node being processed
28          
29            Returns:
30                Sum of values in the subtree rooted at node that fall within [low, high]
31            """
32            # Base case: if node is None, contribute 0 to the sum
33            if node is None:
34                return 0
35          
36            current_val = node.val
37          
38            # Add current node's value to sum if it's within the range
39            current_sum = current_val if low <= current_val <= high else 0
40          
41            # Explore left subtree only if current value is greater than low
42            # (BST property: left subtree contains smaller values)
43            if current_val > low:
44                current_sum += dfs(node.left)
45          
46            # Explore right subtree only if current value is less than high
47            # (BST property: right subtree contains larger values)
48            if current_val < high:
49                current_sum += dfs(node.right)
50          
51            return current_sum
52      
53        # Start DFS traversal from the root
54        return dfs(root)
55
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    /**
18     * Calculates the sum of all node values in a BST that fall within the given range [low, high].
19     * 
20     * @param root The root node of the binary search tree
21     * @param low The lower bound of the range (inclusive)
22     * @param high The upper bound of the range (inclusive)
23     * @return The sum of all node values within the specified range
24     */
25    public int rangeSumBST(TreeNode root, int low, int high) {
26        return dfs(root, low, high);
27    }
28
29    /**
30     * Performs a depth-first search to calculate the range sum.
31     * Utilizes BST properties to prune unnecessary branches:
32     * - If current value > low, explore left subtree (might contain values >= low)
33     * - If current value < high, explore right subtree (might contain values <= high)
34     * 
35     * @param root The current node being processed
36     * @param low The lower bound of the range (inclusive)
37     * @param high The upper bound of the range (inclusive)
38     * @return The sum of node values in the subtree rooted at 'root' that fall within [low, high]
39     */
40    private int dfs(TreeNode root, int low, int high) {
41        // Base case: null node contributes 0 to the sum
42        if (root == null) {
43            return 0;
44        }
45      
46        // Get current node's value
47        int currentValue = root.val;
48      
49        // Add current value to sum if it's within the range [low, high]
50        int sum = (low <= currentValue && currentValue <= high) ? currentValue : 0;
51      
52        // Explore left subtree if there might be values >= low
53        // (BST property: left subtree has smaller values)
54        if (currentValue > low) {
55            sum += dfs(root.left, low, high);
56        }
57      
58        // Explore right subtree if there might be values <= high
59        // (BST property: right subtree has larger values)
60        if (currentValue < high) {
61            sum += dfs(root.right, low, high);
62        }
63      
64        return sum;
65    }
66}
67
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    /**
15     * Calculate the sum of all node values in a BST that fall within [low, high] range
16     * @param root: Root node of the binary search tree
17     * @param low: Lower bound of the range (inclusive)
18     * @param high: Upper bound of the range (inclusive)
19     * @return: Sum of all node values within the specified range
20     */
21    int rangeSumBST(TreeNode* root, int low, int high) {
22        // Define a recursive lambda function to traverse the BST
23        function<int(TreeNode*)> calculateRangeSum = [&](TreeNode* node) -> int {
24            // Base case: if node is null, contribute 0 to the sum
25            if (!node) {
26                return 0;
27            }
28          
29            int currentValue = node->val;
30            int sum = 0;
31          
32            // If current value is within range, include it in the sum
33            if (currentValue >= low && currentValue <= high) {
34                sum = currentValue;
35            }
36          
37            // Optimize traversal using BST properties:
38            // Only explore left subtree if current value is greater than low
39            // (left subtree might contain values >= low)
40            if (currentValue > low) {
41                sum += calculateRangeSum(node->left);
42            }
43          
44            // Only explore right subtree if current value is less than high
45            // (right subtree might contain values <= high)
46            if (currentValue < high) {
47                sum += calculateRangeSum(node->right);
48            }
49          
50            return sum;
51        };
52      
53        // Start the traversal from root
54        return calculateRangeSum(root);
55    }
56};
57
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Calculates the sum of all node values in a BST that fall within the given range [low, high]
17 * @param root - The root node of the binary search tree
18 * @param low - The lower bound of the range (inclusive)
19 * @param high - The upper bound of the range (inclusive)
20 * @returns The sum of all node values within the specified range
21 */
22function rangeSumBST(root: TreeNode | null, low: number, high: number): number {
23    // Helper function to perform depth-first search traversal
24    const calculateRangeSum = (currentNode: TreeNode | null): number => {
25        // Base case: if node is null, return 0
26        if (!currentNode) {
27            return 0;
28        }
29      
30        // Destructure node properties for cleaner access
31        const { val: nodeValue, left: leftChild, right: rightChild } = currentNode;
32      
33        // Initialize sum with current node's value if it's within range, otherwise 0
34        let sum: number = (low <= nodeValue && nodeValue <= high) ? nodeValue : 0;
35      
36        // If current value is greater than low, there might be valid values in left subtree
37        // (BST property: left subtree contains smaller values)
38        if (nodeValue > low) {
39            sum += calculateRangeSum(leftChild);
40        }
41      
42        // If current value is less than high, there might be valid values in right subtree
43        // (BST property: right subtree contains larger values)
44        if (nodeValue < high) {
45            sum += calculateRangeSum(rightChild);
46        }
47      
48        return sum;
49    };
50  
51    // Start the traversal from root
52    return calculateRangeSum(root);
53}
54

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the binary search tree.

In the worst case, the algorithm visits every node in the tree exactly once. Even though the code has optimizations that skip certain subtrees based on BST properties (when x <= low, it skips the left subtree; when x >= high, it skips the right subtree), the worst-case scenario occurs when all nodes fall within the range [low, high]. In this case, every node must be visited to calculate the sum, resulting in O(n) time complexity.

Space Complexity: O(n) where n is the number of nodes in the binary search tree.

The space complexity is determined by the recursive call stack. In the worst case, the tree could be completely unbalanced (essentially a linked list), causing the recursion depth to reach n levels. Each recursive call adds a frame to the call stack, consuming O(1) space per frame. Therefore, the maximum space used by the call stack is O(n) in the worst case. In the best case of a balanced tree, the space complexity would be O(log n), but we consider the worst-case complexity here.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Pruning Logic - Using Wrong Comparison Operators

One of the most common mistakes is getting the pruning conditions backwards or using the wrong comparison operators. Developers often write:

Incorrect Implementation:

# WRONG: Using >= and <= for pruning conditions
if current_val >= low:  # Should be >
    current_sum += dfs(node.left)
if current_val <= high:  # Should be <
    current_sum += dfs(node.right)

Why This Is Wrong:

  • When current_val == low, all values in the left subtree are < low (due to BST property), so exploring the left subtree is unnecessary
  • When current_val == high, all values in the right subtree are > high, so exploring the right subtree is unnecessary
  • Using >= and <= causes unnecessary traversals and defeats the optimization

Correct Implementation:

# CORRECT: Use strict inequalities for pruning
if current_val > low:
    current_sum += dfs(node.left)
if current_val < high:
    current_sum += dfs(node.right)

2. Forgetting to Add Current Node's Value When in Range

Another pitfall is exploring the subtrees correctly but forgetting to include the current node's value when it falls within the range:

Incorrect Implementation:

def dfs(node):
    if node is None:
        return 0
  
    current_sum = 0  # WRONG: Not checking if current node is in range
  
    if node.val > low:
        current_sum += dfs(node.left)
    if node.val < high:
        current_sum += dfs(node.right)
  
    return current_sum

Correct Implementation:

def dfs(node):
    if node is None:
        return 0
  
    # CORRECT: Check and add current node's value if in range
    current_sum = node.val if low <= node.val <= high else 0
  
    if node.val > low:
        current_sum += dfs(node.left)
    if node.val < high:
        current_sum += dfs(node.right)
  
    return current_sum

3. Using 'elif' Instead of Independent 'if' Statements

Some developers mistakenly use elif for the second pruning condition:

Incorrect Implementation:

if current_val > low:
    current_sum += dfs(node.left)
elif current_val < high:  # WRONG: Should be 'if', not 'elif'
    current_sum += dfs(node.right)

Why This Is Wrong:

  • When a node's value is within the range (e.g., low < current_val < high), we need to explore BOTH subtrees
  • Using elif means we'll only explore one subtree even when both should be explored
  • This leads to missing valid nodes and incorrect sums

Correct Implementation:

if current_val > low:
    current_sum += dfs(node.left)
if current_val < high:  # CORRECT: Independent 'if' allows both to execute
    current_sum += dfs(node.right)

Prevention Tips:

  1. Test with edge cases: Use a BST where the root value equals low or high to verify pruning logic
  2. Trace through small examples: Manually trace the algorithm on a small BST to ensure all valid nodes are visited
  3. Remember the BST property: Always keep in mind that left < current < right to reason about pruning conditions
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More