Facebook Pixel

563. Binary Tree Tilt

Problem Description

You are given the root of a binary tree. Your task is to calculate the sum of tilts for all nodes in the tree.

The tilt of a node is defined as the absolute difference between:

  • The sum of all node values in its left subtree
  • The sum of all node values in its right subtree

For nodes without a left child, the sum of the left subtree is considered 0. The same rule applies for nodes without a right child.

The goal is to return the total sum of tilts for every node in the tree.

Example breakdown:

  • For each node, calculate the sum of all values in its left subtree
  • Calculate the sum of all values in its right subtree
  • The tilt of that node = |left_sum - right_sum|
  • Add up all the tilts from every node in the tree

The solution uses a depth-first search (DFS) approach where:

  1. The function recursively calculates the sum of node values for each subtree
  2. While calculating these sums, it also computes the tilt at each node
  3. The tilt values are accumulated in a running total
  4. The function returns both the sum needed by parent nodes and maintains the tilt sum

The key insight is that we can calculate both the sum of subtree values (needed for parent calculations) and the tilt contribution in a single traversal of the 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 tree is a special type of graph where each node has at most two children and there are no cycles.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure.

DFS

  • We arrive at DFS: For tree problems, DFS is the natural choice for traversing and processing nodes.

Conclusion: The flowchart suggests using a Depth-First Search (DFS) approach for solving the Binary Tree Tilt problem.

This makes sense because:

  1. We need to visit every node in the tree to calculate its tilt
  2. To calculate a node's tilt, we first need information from its children (the sum of values in left and right subtrees)
  3. DFS allows us to process children before their parents, enabling bottom-up computation
  4. The recursive nature of DFS perfectly matches the recursive structure of the tree

The DFS pattern helps us:

  • Traverse to the leaf nodes first
  • Calculate subtree sums while returning from recursive calls
  • Compute each node's tilt using the subtree sums
  • Accumulate the total tilt sum across all nodes in a single traversal
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we look at this problem, we need to calculate the tilt for every single node, where each node's tilt depends on the sum of all values in its left and right subtrees. This immediately suggests we need information from children before we can process a parent node - a classic bottom-up pattern.

Think about it this way: to find the tilt of a node, we need two pieces of information:

  1. The sum of all values in its left subtree
  2. The sum of all values in its right subtree

But here's the key insight - when we're calculating these sums for a parent node, we're essentially calculating the same information that its parent will need later. This means we can solve two problems at once:

  • Calculate the tilt for the current node
  • Return the sum of the current subtree for the parent's calculation

This dual-purpose approach naturally leads us to a recursive DFS solution where each recursive call does two things:

  1. Returns the sum of all nodes in the subtree (needed by the parent)
  2. Accumulates the tilt value in a global variable (our final answer)

The beauty of this approach is that we only traverse the tree once. As we bubble up from the leaves to the root:

  • Leaf nodes have tilt = 0 (no children)
  • Each parent calculates its tilt using the sums returned by its children
  • We keep a running total of all tilts encountered

The formula at each node becomes:

  • tilt = |left_sum - right_sum|
  • return left_sum + right_sum + current_node_value

This way, each node provides exactly what its parent needs while contributing to our final answer - an elegant solution that leverages the recursive nature of trees.

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

Solution Approach

The implementation uses a recursive DFS function that calculates both the subtree sum and the tilt contribution in a single traversal.

Let's break down the solution step by step:

1. Setting up the DFS function: We define an inner function dfs(root) that will:

  • Return the sum of all node values in the subtree rooted at root
  • Calculate and accumulate the tilt for the current node

2. Base case:

if root is None:
    return 0

When we reach a null node, we return 0 as the sum, which handles leaf nodes' missing children naturally.

3. Recursive calls:

l, r = dfs(root.left), dfs(root.right)

We recursively calculate:

  • l: sum of all values in the left subtree
  • r: sum of all values in the right subtree

4. Calculate and accumulate tilt:

nonlocal ans
ans += abs(l - r)

The tilt of the current node is |l - r|. We use nonlocal to access the ans variable from the outer scope and add the current node's tilt to our running total.

5. Return subtree sum:

return l + r + root.val

We return the total sum of the current subtree, which includes:

  • Sum of left subtree (l)
  • Sum of right subtree (r)
  • Current node's value (root.val)

6. Main function execution:

ans = 0
dfs(root)
return ans

We initialize our answer to 0, call the DFS function starting from the root, and return the accumulated tilt sum.

Time Complexity: O(n) where n is the number of nodes, as we visit each node exactly once.

Space Complexity: O(h) where h is the height of the tree, due to the recursive call stack. In the worst case (skewed tree), this could be O(n).

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to understand how the solution works.

Consider this binary tree:

       1
      / \
     2   3
    / \
   4   5

We'll trace through the DFS traversal and show how tilts are calculated bottom-up.

Step 1: Start DFS from root (node 1)

  • Calls dfs(node 2) for left subtree
  • Will later call dfs(node 3) for right subtree

Step 2: Process node 2's left subtree

  • Calls dfs(node 4)
  • Node 4 is a leaf, so:
    • l = dfs(None) = 0
    • r = dfs(None) = 0
    • Tilt of node 4 = |0 - 0| = 0
    • ans = 0 (first tilt added)
    • Returns 0 + 0 + 4 = 4

Step 3: Process node 2's right subtree

  • Calls dfs(node 5)
  • Node 5 is a leaf, so:
    • l = dfs(None) = 0
    • r = dfs(None) = 0
    • Tilt of node 5 = |0 - 0| = 0
    • ans = 0 (still 0)
    • Returns 0 + 0 + 5 = 5

Step 4: Complete processing node 2

  • l = 4 (sum from left subtree)
  • r = 5 (sum from right subtree)
  • Tilt of node 2 = |4 - 5| = 1
  • ans = 1 (accumulating tilts)
  • Returns 4 + 5 + 2 = 11

Step 5: Process node 3

  • Node 3 is a leaf, so:
    • l = dfs(None) = 0
    • r = dfs(None) = 0
    • Tilt of node 3 = |0 - 0| = 0
    • ans = 1 (unchanged)
    • Returns 0 + 0 + 3 = 3

Step 6: Complete processing root (node 1)

  • l = 11 (sum of entire left subtree: 2+4+5)
  • r = 3 (sum of entire right subtree: just 3)
  • Tilt of node 1 = |11 - 3| = 8
  • ans = 1 + 8 = 9
  • Returns 11 + 3 + 1 = 15 (not used)

Final Result: The sum of all tilts = 9

Breaking down the tilts:

  • Node 4: tilt = 0
  • Node 5: tilt = 0
  • Node 2: tilt = 1 (|4-5|)
  • Node 3: tilt = 0
  • Node 1: tilt = 8 (|11-3|)
  • Total: 0 + 0 + 1 + 0 + 8 = 9

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
8from typing import Optional
9
10class Solution:
11    def findTilt(self, root: Optional[TreeNode]) -> int:
12        """
13        Calculate the tilt of the entire binary tree.
14        The tilt of a tree node is the absolute difference between 
15        the sum of all left subtree node values and all right subtree node values.
16        """
17        # Initialize total tilt sum
18        self.total_tilt = 0
19      
20        def calculate_sum_and_tilt(node: Optional[TreeNode]) -> int:
21            """
22            Post-order traversal to calculate the sum of subtree values
23            and accumulate the tilt for each node.
24          
25            Returns the sum of all values in the subtree rooted at 'node'.
26            """
27            # Base case: empty node contributes 0 to sum
28            if node is None:
29                return 0
30          
31            # Recursively calculate left and right subtree sums
32            left_sum = calculate_sum_and_tilt(node.left)
33            right_sum = calculate_sum_and_tilt(node.right)
34          
35            # Calculate tilt for current node and add to total
36            current_tilt = abs(left_sum - right_sum)
37            self.total_tilt += current_tilt
38          
39            # Return sum of current subtree (left + right + current node value)
40            return left_sum + right_sum + node.val
41      
42        # Start the traversal from root
43        calculate_sum_and_tilt(root)
44      
45        return self.total_tilt
46
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    // Instance variable to accumulate the total tilt of all nodes
18    private int totalTilt;
19
20    /**
21     * Finds the sum of all node tilts in the binary tree.
22     * The tilt of a node is the absolute difference between 
23     * the sum of all left subtree node values and the sum of 
24     * all right subtree node values.
25     * 
26     * @param root The root of the binary tree
27     * @return The sum of all node tilts in the tree
28     */
29    public int findTilt(TreeNode root) {
30        totalTilt = 0;
31        calculateSubtreeSum(root);
32        return totalTilt;
33    }
34
35    /**
36     * Recursively calculates the sum of all nodes in the subtree
37     * while accumulating the tilt for each node.
38     * 
39     * @param node The current node being processed
40     * @return The sum of all node values in the subtree rooted at this node
41     */
42    private int calculateSubtreeSum(TreeNode node) {
43        // Base case: null node contributes 0 to the sum
44        if (node == null) {
45            return 0;
46        }
47      
48        // Recursively calculate sum of left and right subtrees
49        int leftSubtreeSum = calculateSubtreeSum(node.left);
50        int rightSubtreeSum = calculateSubtreeSum(node.right);
51      
52        // Calculate and accumulate the tilt for current node
53        int currentNodeTilt = Math.abs(leftSubtreeSum - rightSubtreeSum);
54        totalTilt += currentNodeTilt;
55      
56        // Return the total sum of this subtree (left + right + current node value)
57        return leftSubtreeSum + rightSubtreeSum + node.val;
58    }
59}
60
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    int findTilt(TreeNode* root) {
15        int totalTilt = 0;
16      
17        // Lambda function for DFS traversal
18        // Returns the sum of all node values in the subtree rooted at 'node'
19        auto calculateSubtreeSum = [&](this auto&& calculateSubtreeSum, TreeNode* node) -> int {
20            // Base case: empty node contributes 0 to the sum
21            if (!node) {
22                return 0;
23            }
24          
25            // Recursively calculate sum of left and right subtrees
26            int leftSum = calculateSubtreeSum(node->left);
27            int rightSum = calculateSubtreeSum(node->right);
28          
29            // Calculate tilt for current node and add to total
30            // Tilt = absolute difference between left and right subtree sums
31            totalTilt += abs(leftSum - rightSum);
32          
33            // Return sum of current subtree (left + right + current node value)
34            return leftSum + rightSum + node->val;
35        };
36      
37        // Start DFS traversal from root
38        calculateSubtreeSum(root);
39      
40        return totalTilt;
41    }
42};
43
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 tilts in a binary tree.
17 * The tilt of a node is the absolute difference between the sum of all left subtree node values
18 * and the sum of all right subtree node values.
19 * 
20 * @param root - The root node of the binary tree
21 * @returns The sum of all node tilts in the tree
22 */
23function findTilt(root: TreeNode | null): number {
24    // Variable to accumulate the total tilt of all nodes
25    let totalTilt: number = 0;
26  
27    /**
28     * Performs depth-first search to calculate subtree sums and update total tilt.
29     * 
30     * @param node - The current node being processed
31     * @returns The sum of all node values in the subtree rooted at the current node
32     */
33    const calculateSubtreeSum = (node: TreeNode | null): number => {
34        // Base case: empty node contributes 0 to the sum
35        if (!node) {
36            return 0;
37        }
38      
39        // Recursively calculate the sum of left and right subtrees
40        const leftSubtreeSum: number = calculateSubtreeSum(node.left);
41        const rightSubtreeSum: number = calculateSubtreeSum(node.right);
42      
43        // Calculate and add the tilt of the current node to the total
44        totalTilt += Math.abs(leftSubtreeSum - rightSubtreeSum);
45      
46        // Return the sum of the entire subtree rooted at the current node
47        return leftSubtreeSum + rightSubtreeSum + node.val;
48    };
49  
50    // Start the DFS traversal from the root
51    calculateSubtreeSum(root);
52  
53    return totalTilt;
54}
55

Time and Space Complexity

Time Complexity: O(n)

The algorithm performs a depth-first search (DFS) traversal of the binary tree. Each node is visited exactly once during the traversal. At each node, the algorithm performs constant time operations:

  • Calculating the absolute difference between left and right subtree sums: abs(l - r) - O(1)
  • Adding to the running total: ans += abs(l - r) - O(1)
  • Returning the sum: l + r + root.val - O(1)

Since we visit all n nodes exactly once and perform O(1) operations at each node, the total time complexity is O(n).

Space Complexity: O(n)

The space complexity is determined by the recursive call stack used during DFS traversal. In the worst case, the tree is completely unbalanced (e.g., a skewed tree where each node has only one child), resulting in a recursion depth equal to the number of nodes n. This would require O(n) space on the call stack.

In the best case (a perfectly balanced tree), the recursion depth would be O(log n), but we consider the worst-case scenario for space complexity analysis, which gives us O(n).

The only additional space used is the ans variable, which is O(1), so the overall space complexity remains O(n).

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

Common Pitfalls

1. Confusing Node Tilt with Subtree Sum

A frequent mistake is calculating the tilt using the node values themselves rather than the sum of all nodes in each subtree.

Incorrect approach:

# WRONG: Using only direct children values
tilt = abs(node.left.val - node.right.val)

Correct approach:

# RIGHT: Using sum of entire subtrees
left_sum = calculate_sum_and_tilt(node.left)  # Sum of ALL nodes in left subtree
right_sum = calculate_sum_and_tilt(node.right)  # Sum of ALL nodes in right subtree
tilt = abs(left_sum - right_sum)

2. Forgetting to Include Current Node Value in Return

Another common error is returning only the sum of left and right subtrees without including the current node's value, which breaks the calculation for parent nodes.

Incorrect approach:

def dfs(node):
    if not node:
        return 0
    left_sum = dfs(node.left)
    right_sum = dfs(node.right)
    self.total_tilt += abs(left_sum - right_sum)
    return left_sum + right_sum  # WRONG: Missing node.val

Correct approach:

def dfs(node):
    if not node:
        return 0
    left_sum = dfs(node.left)
    right_sum = dfs(node.right)
    self.total_tilt += abs(left_sum - right_sum)
    return left_sum + right_sum + node.val  # RIGHT: Include current node value

3. Using Local Variable Instead of Instance/Nonlocal Variable

Attempting to use a regular local variable for accumulating tilts will fail because each recursive call creates its own scope.

Incorrect approach:

def findTilt(self, root):
    total_tilt = 0  # Local variable
  
    def dfs(node):
        if not node:
            return 0
        left_sum = dfs(node.left)
        right_sum = dfs(node.right)
        total_tilt += abs(left_sum - right_sum)  # ERROR: UnboundLocalError
        return left_sum + right_sum + node.val
  
    dfs(root)
    return total_tilt

Correct approaches:

# Option 1: Using instance variable
def findTilt(self, root):
    self.total_tilt = 0
  
    def dfs(node):
        # ... rest of code
        self.total_tilt += abs(left_sum - right_sum)
        # ...
  
    dfs(root)
    return self.total_tilt

# Option 2: Using nonlocal keyword
def findTilt(self, root):
    total_tilt = 0
  
    def dfs(node):
        nonlocal total_tilt
        # ... rest of code
        total_tilt += abs(left_sum - right_sum)
        # ...
  
    dfs(root)
    return total_tilt

4. Calculating Tilt Only for Leaf Nodes

Some might mistakenly think tilt is only calculated for leaf nodes or only for non-leaf nodes.

Incorrect assumption:

def dfs(node):
    if not node:
        return 0
  
    left_sum = dfs(node.left)
    right_sum = dfs(node.right)
  
    # WRONG: Only adding tilt for leaf nodes
    if not node.left and not node.right:
        self.total_tilt += abs(left_sum - right_sum)
  
    return left_sum + right_sum + node.val

Correct approach: Every node (including leaves, internal nodes, and root) contributes to the total tilt. For leaf nodes, the tilt is always 0 (since both subtrees are empty), but we still include them in our calculation.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

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

Load More