979. Distribute Coins in Binary Tree
Problem Description
You have a binary tree with n
nodes, where each node contains some number of coins (stored in node.val
). The total number of coins across all nodes equals n
, meaning there are exactly n
coins distributed throughout the tree.
Your goal is to redistribute these coins so that every node has exactly one coin. You can move coins between adjacent nodes (parent to child or child to parent), with each coin movement counting as one "move."
The task is to find the minimum number of moves required to achieve this distribution where each node has exactly one coin.
For example, if a node has 3 coins, it needs to give away 2 coins to its neighbors. If a node has 0 coins, it needs to receive 1 coin from a neighbor. The challenge is to calculate the total minimum moves needed across the entire tree to balance all nodes to have exactly 1 coin each.
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 with nodes (tree nodes) and edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree where we need to redistribute coins among nodes.
DFS
- We arrive at DFS: Since we're dealing with a tree structure and need to process information from children nodes to parent nodes (calculating coin surplus/deficit from leaves up to root), DFS is the appropriate approach.
Conclusion: The flowchart suggests using a DFS (Depth-First Search) approach for this problem. This makes sense because:
- We need to traverse the entire tree to understand coin distribution
- We need to calculate information bottom-up (from leaves to root) to determine how many coins need to move through each edge
- Each subtree's coin balance affects its parent's calculation, making post-order DFS traversal ideal for accumulating the minimum moves required
Intuition
The key insight is to think about what happens at each subtree. For any node in the tree, we need to ensure that the subtree rooted at that node has exactly the right number of coins for its nodes. If a subtree has 5 nodes, it needs exactly 5 coins total.
Consider what happens when a subtree has excess or deficit coins:
- If a subtree has more coins than nodes (e.g., 7 coins for 5 nodes), the extra 2 coins must flow out through the parent edge
- If a subtree has fewer coins than nodes (e.g., 3 coins for 5 nodes), it needs 2 coins to flow in through the parent edge
The crucial observation is that every coin that needs to move from one subtree to another must pass through the edge connecting that subtree to its parent. Whether we have excess coins to send up or need coins to flow down, the absolute value of this difference represents the number of moves through that edge.
For each node, we can calculate its "balance" as: left_balance + right_balance + node.val - 1
left_balance
: excess/deficit from left subtreeright_balance
: excess/deficit from right subtreenode.val - 1
: the current node's excess/deficit (it hasnode.val
coins but needs exactly 1)
The number of moves required at this node is |left_balance| + |right_balance|
because:
- If left subtree has +3 balance, 3 coins move from left child to current node
- If right subtree has -2 balance, 2 coins move from current node to right child
- Both movements count toward our total moves
By recursively calculating this from the leaves up to the root, we accumulate the total minimum moves needed to balance the entire tree.
Learn more about Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The solution implements a DFS traversal that calculates the coin overload for each subtree. The coin overload represents how many extra coins a subtree has (positive) or how many it needs (negative).
We define a recursive function dfs(root)
that processes each node in post-order traversal:
-
Base Case: If the node is
None
, return 0 (no coin overload for empty subtrees). -
Recursive Calls: First, recursively calculate the coin overload for left and right subtrees:
left = dfs(root.left)
: Get the coin overload from the left subtreeright = dfs(root.right)
: Get the coin overload from the right subtree
-
Count Moves: The number of moves through the current node equals
abs(left) + abs(right)
:abs(left)
: Number of coins that must flow between current node and left childabs(right)
: Number of coins that must flow between current node and right child- Add this to our global answer counter
ans
-
Calculate Current Subtree's Overload: Return the total overload for the subtree rooted at current node:
- Formula:
left + right + root.val - 1
left + right
: Net coins from childrenroot.val
: Coins at current node-1
: One coin that must remain at current node- This value tells the parent how many coins this entire subtree has in excess or deficit
- Formula:
The algorithm uses a nonlocal variable ans
to accumulate the total moves across all nodes. After the DFS completes, ans
contains the minimum number of moves required to distribute exactly one coin to each node.
The time complexity is O(n)
as we visit each node once, and the space complexity is 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 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate how the solution works.
Consider this binary tree:
0 / \ 3 0
We have 3 nodes total and 3 coins total (all at the left child). Our goal is to redistribute so each node has exactly 1 coin.
Step 1: Process the left child (leaf node with value 3)
- This is a leaf node, so
left = 0
andright = 0
(no children) - Calculate moves at this node:
|0| + |0| = 0
- Calculate overload for this subtree:
0 + 0 + 3 - 1 = 2
- This node has 2 extra coins that need to flow up to its parent
- Running total moves:
0
Step 2: Process the right child (leaf node with value 0)
- This is a leaf node, so
left = 0
andright = 0
- Calculate moves at this node:
|0| + |0| = 0
- Calculate overload for this subtree:
0 + 0 + 0 - 1 = -1
- This node needs 1 coin from its parent
- Running total moves:
0
Step 3: Process the root node (value 0)
- We've already processed its children:
- Left child returns overload of
+2
(has 2 extra coins) - Right child returns overload of
-1
(needs 1 coin)
- Left child returns overload of
- Calculate moves at this node:
|2| + |-1| = 2 + 1 = 3
- 2 coins must move from left child to root
- 1 coin must move from root to right child
- Running total moves:
0 + 3 = 3
- Calculate overload for entire tree:
2 + (-1) + 0 - 1 = 0
- This should always be 0 for the root since total coins equals total nodes
Final Distribution: After 3 moves, the tree becomes:
1 / \ 1 1
The moves were:
- Move 1 coin from left child to root
- Move another coin from left child to root
- Move 1 coin from root to right child
This matches our calculated answer of 3 minimum moves.
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 distributeCoins(self, root: Optional[TreeNode]) -> int:
12 """
13 Distributes coins in a binary tree so each node has exactly 1 coin.
14 Returns the minimum number of moves required.
15
16 Args:
17 root: Root of the binary tree where each node contains coins
18
19 Returns:
20 Minimum number of moves to distribute coins evenly
21 """
22
23 def calculate_balance(node: Optional[TreeNode]) -> int:
24 """
25 Calculates the coin balance for a subtree rooted at 'node'.
26 Balance = total coins in subtree - number of nodes in subtree
27
28 Args:
29 node: Current node being processed
30
31 Returns:
32 Excess or deficit of coins in the subtree
33 Positive value means excess coins, negative means deficit
34 """
35 # Base case: empty node contributes nothing
36 if node is None:
37 return 0
38
39 # Recursively calculate balance for left and right subtrees
40 left_balance = calculate_balance(node.left)
41 right_balance = calculate_balance(node.right)
42
43 # Each coin that needs to move through current node counts as a move
44 # abs(balance) represents coins moving in or out of subtree
45 nonlocal total_moves
46 total_moves += abs(left_balance) + abs(right_balance)
47
48 # Return balance for current subtree:
49 # (coins from left) + (coins from right) + (current node's coins) - 1
50 # The -1 accounts for keeping one coin at current node
51 return left_balance + right_balance + node.val - 1
52
53 # Initialize counter for total moves required
54 total_moves = 0
55
56 # Start DFS traversal from root
57 calculate_balance(root)
58
59 return total_moves
60
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 // Total number of moves required to distribute coins
18 private int totalMoves;
19
20 /**
21 * Distributes coins in a binary tree so that every node has exactly one coin.
22 * Returns the minimum number of moves required.
23 *
24 * @param root The root of the binary tree
25 * @return The minimum number of moves to distribute coins
26 */
27 public int distributeCoins(TreeNode root) {
28 totalMoves = 0;
29 calculateExcessCoins(root);
30 return totalMoves;
31 }
32
33 /**
34 * Performs a post-order DFS traversal to calculate excess/deficit coins at each node.
35 *
36 * The key insight: Each node needs exactly 1 coin. If a subtree has excess coins,
37 * they must flow through the parent. If it has a deficit, coins must flow in.
38 *
39 * @param node The current node being processed
40 * @return The excess (positive) or deficit (negative) of coins in this subtree
41 */
42 private int calculateExcessCoins(TreeNode node) {
43 // Base case: null nodes contribute nothing
44 if (node == null) {
45 return 0;
46 }
47
48 // Recursively calculate excess/deficit for left and right subtrees
49 int leftExcess = calculateExcessCoins(node.left);
50 int rightExcess = calculateExcessCoins(node.right);
51
52 // The number of moves through this node equals the absolute value of coins
53 // that need to flow from/to each subtree
54 totalMoves += Math.abs(leftExcess) + Math.abs(rightExcess);
55
56 // Calculate this subtree's total excess/deficit:
57 // - Add coins from left subtree
58 // - Add coins from right subtree
59 // - Add this node's coins
60 // - Subtract 1 (the coin this node needs to keep)
61 return leftExcess + rightExcess + node.val - 1;
62 }
63}
64
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 distributeCoins(TreeNode* root) {
15 int totalMoves = 0;
16
17 // Post-order DFS to calculate coin excess/deficit for each subtree
18 // Returns: excess coins (positive) or deficit (negative) for the subtree
19 function<int(TreeNode*)> calculateExcess = [&](TreeNode* node) -> int {
20 // Base case: null node has no excess or deficit
21 if (!node) {
22 return 0;
23 }
24
25 // Recursively calculate excess/deficit for left and right subtrees
26 int leftExcess = calculateExcess(node->left);
27 int rightExcess = calculateExcess(node->right);
28
29 // The number of moves needed is the absolute value of coins flowing
30 // through this node from/to its children
31 totalMoves += abs(leftExcess) + abs(rightExcess);
32
33 // Calculate this node's excess/deficit:
34 // - Add excess from children (leftExcess + rightExcess)
35 // - Add this node's coins (node->val)
36 // - Subtract 1 (the coin this node needs to keep)
37 return leftExcess + rightExcess + node->val - 1;
38 };
39
40 calculateExcess(root);
41 return totalMoves;
42 }
43};
44
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 * Distributes coins in a binary tree so each node has exactly one coin.
17 * Returns the minimum number of moves required.
18 *
19 * @param root - The root of the binary tree
20 * @returns The minimum number of moves to distribute coins
21 */
22function distributeCoins(root: TreeNode | null): number {
23 // Total number of moves required
24 let totalMoves: number = 0;
25
26 /**
27 * Performs depth-first search to calculate excess coins at each node.
28 * Returns the number of excess coins (positive) or deficit (negative) for the subtree.
29 *
30 * @param node - Current node being processed
31 * @returns Excess/deficit coins for the subtree rooted at this node
32 */
33 const calculateExcessCoins = (node: TreeNode | null): number => {
34 // Base case: null node contributes 0 excess coins
35 if (!node) {
36 return 0;
37 }
38
39 // Recursively calculate excess coins from left and right subtrees
40 const leftExcess: number = calculateExcessCoins(node.left);
41 const rightExcess: number = calculateExcessCoins(node.right);
42
43 // The number of moves is the absolute value of excess coins flowing through edges
44 totalMoves += Math.abs(leftExcess) + Math.abs(rightExcess);
45
46 // Return total excess: coins from children + current node's coins - 1 (node keeps one)
47 return leftExcess + rightExcess + node.val - 1;
48 };
49
50 // Start the DFS traversal from root
51 calculateExcessCoins(root);
52
53 return totalMoves;
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:
- Checking if the node is
None
- Calculating the excess/deficit of coins (
left + right + root.val - 1
) - Updating the answer with
abs(left) + abs(right)
Since we visit all n
nodes exactly once and perform O(1)
operations at each node, the overall time complexity is O(n)
.
Space Complexity: O(h)
The space complexity is determined by the recursive call stack used during the DFS traversal. In the worst case, the recursion depth equals the height of the binary tree:
- For a balanced binary tree, the height
h = O(log n)
, resulting inO(log n)
space complexity - For a skewed binary tree (essentially a linked list), the height
h = O(n)
, resulting inO(n)
space complexity
Therefore, the space complexity is O(h)
where h
is the height of the binary tree. No additional data structures are used that scale with the input size; only the ans
variable uses O(1)
extra space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Balance Calculation
A common mistake is incorrectly calculating what each subtree should return. Developers often confuse:
- The number of moves (which we accumulate in
total_moves
) - The coin balance/overload (which we return from the function)
Incorrect approach:
def calculate_balance(node):
if node is None:
return 0
left_balance = calculate_balance(node.left)
right_balance = calculate_balance(node.right)
# WRONG: Returning moves instead of balance
return abs(left_balance) + abs(right_balance)
Correct approach: The function should return the net coin surplus/deficit for the subtree, not the number of moves.
2. Forgetting to Use Absolute Values for Move Counting
When counting moves, both positive (excess) and negative (deficit) balances represent coins that must move. Forgetting to use absolute values leads to incorrect move counts.
Incorrect:
# WRONG: Not using absolute values total_moves += left_balance + right_balance
Correct:
# RIGHT: Both excess and deficit require moves
total_moves += abs(left_balance) + abs(right_balance)
3. Off-by-One Error in Balance Formula
The formula left + right + root.val - 1
is crucial. Common mistakes include:
- Forgetting the
-1
(which accounts for keeping one coin at the current node) - Using
+1
instead of-1
Incorrect:
# WRONG: Forgot to subtract 1 return left_balance + right_balance + node.val # WRONG: Added 1 instead of subtracting return left_balance + right_balance + node.val + 1
Correct:
# RIGHT: Subtract 1 for the coin that stays at current node return left_balance + right_balance + node.val - 1
4. Using Global Variables Instead of Nonlocal
In Python, when modifying a variable from an outer scope inside a nested function, you must declare it as nonlocal
. Using global
or forgetting the declaration entirely causes errors.
Incorrect:
def distributeCoins(self, root):
total_moves = 0
def calculate_balance(node):
# WRONG: Missing nonlocal declaration
total_moves += abs(left_balance) + abs(right_balance)
# This will raise UnboundLocalError
Correct:
def distributeCoins(self, root):
total_moves = 0
def calculate_balance(node):
nonlocal total_moves # Correct declaration
total_moves += abs(left_balance) + abs(right_balance)
5. Not Handling Edge Cases
While the problem guarantees n
nodes with n
coins total, implementations should still handle:
- Single node trees (where no moves are needed)
- Skewed trees (all nodes in a line)
These cases work correctly with the standard algorithm but can trip up alternative approaches that make assumptions about tree structure.
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!