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:
- The function recursively calculates the sum of node values for each subtree
- While calculating these sums, it also computes the tilt at each node
- The tilt values are accumulated in a running total
- 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:
- We need to visit every node in the tree to calculate its tilt
- To calculate a node's tilt, we first need information from its children (the sum of values in left and right subtrees)
- DFS allows us to process children before their parents, enabling bottom-up computation
- 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
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:
- The sum of all values in its left subtree
- 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:
- Returns the sum of all nodes in the subtree (needed by the parent)
- 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 subtreer
: 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 EvaluatorExample 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.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!