Builds on Bottom-Up DFS and Tree Diameter DP, which you haven’t finished yet.
Greedy Postorder Tree DP (Distribute Coins, Minimum Fuel Cost)
You're given the root of an n-node binary tree where each node is holding node.val coins, and the coins across the whole tree add up to exactly n.
A move slides a single coin between two nodes that are directly connected — parent to child, or child to parent.
Work out the minimum number of moves needed so every node ends up holding exactly one coin.
Greedy Tree DP
Postorder Child State Code ➔ Greedy Parent Action| 1 | from typing import Optional |
| 2 | |
| 3 | class TreeNode: |
| 4 | def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None): |
| 5 | self.val = val |
| 6 | self.left = left |
| 7 | self.right = right |
| 8 | |
| 9 | def distribute_coins(root: Optional[TreeNode]) -> int: |
| 10 | """Distribute Coins in Binary Tree: fewest moves so every node ends with exactly one |
| 11 | coin. |
| 12 | Time: O(N) | Space: O(H) recursion stack |
| 13 | """ |
| 14 | moves = 0 |
| 15 | |
| 16 | def excess(node: Optional[TreeNode]) -> int: |
| 17 | nonlocal moves |
| 18 | if not node: |
| 19 | return 0 # An empty subtree neither needs nor gives coins |
| 20 | left = excess(node.left) # Postorder: children report first |
| 21 | right = excess(node.right) |
| 22 | # Each surplus or debt crosses that child's edge |
| 23 | moves += abs(left) + abs(right) |
| 24 | # Signed surplus: coins minus nodes in this subtree |
| 25 | return node.val + left + right - 1 |
| 26 | |
| 27 | excess(root) |
| 28 | return moves |
excess(node) returns the coins in node's subtree minus its number of nodes, and moves already counts every coin that must cross an edge below node.Coins can enter or leave a subtree only through the edge to its parent, so let excess(node) return that subtree's surplus, node.val + left + right - 1 (coins minus nodes), and add abs(left) + abs(right) to moves for the two child edges. A surplus of 3, or a debt of 3, forces at least 3 coins across that edge, and settling each subtree with its parent in postorder never moves a coin more than that, so the sum is the minimum. The trap is where abs goes: return the signed surplus and take abs only when adding to moves; a subtree that is short 2 coins must return -2, or its parent treats the debt as spare coins.
See it run
Greedy tree DP: the signed excess, step by step
returns -moves 0answer not yet
Each circle is a tree node. It shows the coins the node holds. A finished node shows its value, an arrow, and the signed excess its call returned. A positive excess is spare coins and a negative excess is a debt.
at nodethe node being finishedexcessa finished node, with the signed excess it returnedcoins crossan edge that coins must cross, one coin per move
Every node must end with exactly 1 coin. A circle shows the coins a node holds now.
Gotcha 1 of 3
- The trap
Gotcha 1: Returning
abs(...)of the surplus instead of the signed surplusMistakereturn abs(node.val + left + right - 1)Fixreturn node.val + left + right - 1What happens: A subtree that is short of coins returns a negative number. The parent adds that debt to its own count. With
abs, a debt of 1 comes back as a surplus of 1. The parent then counts coins that do not exist. In the example, the code counts 2 moves instead of 4. Takeabsonly when adding tomoves.Example: root = [2,0,2,0]Mistake: returns 2Fix: returns 4
- You look for one best path that may bend at any node, as in Binary Tree Maximum Path Sum (LC 124), not a total cost added up over every edge. See Tree Diameter DP
Deep Dive
Why one signed number per subtree is enough
In Distribute Coins in Binary Tree (LC 979), a move takes one coin across one edge. Every node must end with exactly one coin. The total number of coins equals the number of nodes.
Look at one subtree and the edge that joins it to its parent. Coins can enter or leave the subtree only through that edge. If the subtree holds 3 more coins than nodes, 3 coins must leave through that edge. If it holds 2 fewer, 2 coins must enter. So the subtree needs exactly one number to describe what crosses the edge: its coins minus its nodes. This is excess(node). A positive excess is spare coins. A negative excess is a debt.
Take root = [0, 3, 0]. The left child holds 3 coins and is one node, so it returns 3 - 1 = +2. The right child holds 0 coins, so it returns 0 - 1 = -1. At the root, moves grows by |+2| + |-1| = 3. The root returns 0 + 2 + (-1) - 1 = 0, because the coins equal the nodes. The answer is 3: two coins leave the left child, one stays at the root and one goes on to the right child.
Why the sum of absolute excesses is the fewest moves
There is a lower bound for each edge. If the subtree below an edge has excess e, then at least |e| coins must cross that edge. A move crosses only one edge, so the fewest moves is at least the sum of |e| over all edges.
The bound can be reached. Settle the tree in postorder. When a node is handled, its children have already passed their surplus up or asked for coins, so the node can pass spare coins to a child that has a debt, or send them up. No coin has to cross an edge in both directions, and no edge is crossed more than |e| times. So the sum is not only a lower bound but also the answer.
This is why the code adds abs(left) + abs(right) to moves at each node. The two terms are the two child edges. Each edge is counted once, at its upper node.
A second example: root = [1, 0, 0, null, 3]. The node 3 returns +2. Its parent, the left child 0, adds |0| + |2| = 2 to moves and returns 0 + 0 + 2 - 1 = +1. The right child 0 returns -1. The root adds |+1| + |-1| = 2, so moves is 4. The root returns 0.
Two traps: where abs goes, and the minus 1
The first trap is abs in the return statement. The function must return the signed excess. A subtree that is short of coins must return a negative number. If the function returns abs(node.val + left + right - 1), a debt of 1 comes back as a surplus of 1, and the parent counts coins that do not exist. For root = [2, 0, 2, 0] the correct answer is 4, and the code with abs in the return gives 2. Take abs only when adding to moves.
The second trap is the - 1. Every node keeps one coin, so the excess of a subtree is its coins minus its nodes, and the node itself counts as one. A leaf with one coin must return 0. Without the - 1, that leaf returns 1, as if it had a spare coin. For root = [3, 0, 0] the correct answer is 2, and the code without - 1 gives 0.
A third trap is to return moves from the function. The function returns the excess, and moves is a separate counter kept outside, so it needs nonlocal. The answer is moves after the call on the root ends.
The cost, and how it differs from Tree DP with a bend
Each node is visited once, and each visit does constant work. The time is O(N). The space is O(H) for the recursion stack, where H is the height.
This card is built on Bottom-Up DFS. Each call returns one number for its subtree, and the parent combines the numbers of its children. The difference is that the number is signed, and the parent uses it twice: once to add a cost to moves for the edge below, and once to build its own number.
Compare with the card Tree DP (Max Path Sum / Diameter). There you look for one best path that may bend at any node, and the answer is stored in global_max. Here you add a cost for every edge, and the answer is the total in moves. Every edge is counted, not one chosen path.
Other problems on this card have the same shape: a subtree reports one number, and the parent pays a cost for the edge from that number. Minimum Fuel Cost to Report to the Capital (LC 2477) is one of them.
60-Second Pitch
"Coins can enter or leave a subtree only through the edge to its parent, so one signed number describes the subtree. excess(node) returns node.val + left + right - 1: the coins in the subtree minus its nodes. At each node I add abs(left) + abs(right) to moves, because that many coins must cross the two child edges. I take abs only for moves, and I return the signed value. Each edge needs at least its excess in crossings, and settling in postorder reaches exactly that, so the sum is the minimum. Each node is visited once, so the time is O(N). The space is O(H) for the recursion stack."
Common Questions
- Why is the sum of the absolute excesses the fewest moves?
Each edge has a subtree below it with a fixed excess
e. At least |e| coins must cross that edge, and each move crosses one edge. So the sum is a lower bound. Settling the tree from the leaves up reaches it, because no edge has to be crossed more than |e| times.- Why does the function return a signed number?
The sign tells the parent which way the coins go. A negative excess is a debt that the parent must cover. If the function returned the absolute value, the parent would treat the debt as spare coins and count coins that do not exist.
- What are the time and space complexity?
O(N)time, because each node is visited once with constant work. O(H) space for the recursion stack, whereHis the height.movesis one number.- Why is there a
- 1in the return value? Every node must end with one coin, so the node keeps one. The excess of a subtree is its coins minus its nodes. A leaf with one coin returns 0.
- How is this different from Binary Tree Maximum Path Sum (LC 124)?
Both return one number per subtree from a postorder walk. In Binary Tree Maximum Path Sum (LC 124), the answer is one best path that may bend at any node, and it is stored in
global_max. Here, the answer is a total over every edge, stored inmoves.
SWE Reasoning
Spot it
- Each subtree can be described by one number: what it holds minus what it needs.
- Things can enter or leave a subtree only through the edge to its parent.
- The wording asks for the fewest moves, trips or total cost over all edges, with every node ending in a fixed state.
- A greedy argument says that each edge must carry at least the subtree's imbalance, and the sum of these is reachable.
- The answer is a running total, not a single path.
The invariant, step by step
excess(node)returns the coins in the subtree atnodeminus the number of nodes in it.- A positive value means spare coins must leave through the edge above. A negative value means coins must enter. Either way, |excess| coins cross that edge.
- The base case is an empty subtree. It has no coins and no nodes, so it returns 0.
- At a node, the children report first.
moves += abs(left) + abs(right)counts the coins that cross the two child edges. - The node returns
node.val + left + right - 1, its own signed excess. The- 1is the coin the node keeps. - At the end,
movescounts every edge once with its minimum crossing. That is the fewest moves.
Traps
- Returning
abs(...). A debt becomes a surplus. Return the signed excess and takeabsonly when adding tomoves. - Forgetting
- 1. A node keeps one coin, so the excess is coins minus nodes. - Counting an edge twice. Add the child excess at the upper node only. Do not add it again at the child.
- Returning the excess instead of
moves. The answer is the total inmoves. The root's excess is 0. - Passing up the wrong value in related problems. In Make Costs of Paths Equal in a Binary Tree (LC 2673), the value passed up is the larger cost, not the smaller.
Practice
Builds on Bottom-Up DFS and Tree Diameter DP, which you haven’t finished yet.
Greedy Postorder Tree DP (Distribute Coins, Minimum Fuel Cost)
You're given the root of an n-node binary tree where each node is holding node.val coins, and the coins across the whole tree add up to exactly n.
A move slides a single coin between two nodes that are directly connected — parent to child, or child to parent.
Work out the minimum number of moves needed so every node ends up holding exactly one coin.
Greedy Tree DP
Postorder Child State Code ➔ Greedy Parent Action| 1 | from typing import Optional |
| 2 | |
| 3 | class TreeNode: |
| 4 | def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None): |
| 5 | self.val = val |
| 6 | self.left = left |
| 7 | self.right = right |
| 8 | |
| 9 | def distribute_coins(root: Optional[TreeNode]) -> int: |
| 10 | """Distribute Coins in Binary Tree: fewest moves so every node ends with exactly one |
| 11 | coin. |
| 12 | Time: O(N) | Space: O(H) recursion stack |
| 13 | """ |
| 14 | moves = 0 |
| 15 | |
| 16 | def excess(node: Optional[TreeNode]) -> int: |
| 17 | nonlocal moves |
| 18 | if not node: |
| 19 | return 0 # An empty subtree neither needs nor gives coins |
| 20 | left = excess(node.left) # Postorder: children report first |
| 21 | right = excess(node.right) |
| 22 | # Each surplus or debt crosses that child's edge |
| 23 | moves += abs(left) + abs(right) |
| 24 | # Signed surplus: coins minus nodes in this subtree |
| 25 | return node.val + left + right - 1 |
| 26 | |
| 27 | excess(root) |
| 28 | return moves |
excess(node) returns the coins in node's subtree minus its number of nodes, and moves already counts every coin that must cross an edge below node.Coins can enter or leave a subtree only through the edge to its parent, so let excess(node) return that subtree's surplus, node.val + left + right - 1 (coins minus nodes), and add abs(left) + abs(right) to moves for the two child edges. A surplus of 3, or a debt of 3, forces at least 3 coins across that edge, and settling each subtree with its parent in postorder never moves a coin more than that, so the sum is the minimum. The trap is where abs goes: return the signed surplus and take abs only when adding to moves; a subtree that is short 2 coins must return -2, or its parent treats the debt as spare coins.
See it run
Greedy tree DP: the signed excess, step by step
returns -moves 0answer not yet
Each circle is a tree node. It shows the coins the node holds. A finished node shows its value, an arrow, and the signed excess its call returned. A positive excess is spare coins and a negative excess is a debt.
at nodethe node being finishedexcessa finished node, with the signed excess it returnedcoins crossan edge that coins must cross, one coin per move
Every node must end with exactly 1 coin. A circle shows the coins a node holds now.
Gotcha 1 of 3
- The trap
Gotcha 1: Returning
abs(...)of the surplus instead of the signed surplusMistakereturn abs(node.val + left + right - 1)Fixreturn node.val + left + right - 1What happens: A subtree that is short of coins returns a negative number. The parent adds that debt to its own count. With
abs, a debt of 1 comes back as a surplus of 1. The parent then counts coins that do not exist. In the example, the code counts 2 moves instead of 4. Takeabsonly when adding tomoves.Example: root = [2,0,2,0]Mistake: returns 2Fix: returns 4
- You look for one best path that may bend at any node, as in Binary Tree Maximum Path Sum (LC 124), not a total cost added up over every edge. See Tree Diameter DP
Deep Dive
Why one signed number per subtree is enough
In Distribute Coins in Binary Tree (LC 979), a move takes one coin across one edge. Every node must end with exactly one coin. The total number of coins equals the number of nodes.
Look at one subtree and the edge that joins it to its parent. Coins can enter or leave the subtree only through that edge. If the subtree holds 3 more coins than nodes, 3 coins must leave through that edge. If it holds 2 fewer, 2 coins must enter. So the subtree needs exactly one number to describe what crosses the edge: its coins minus its nodes. This is excess(node). A positive excess is spare coins. A negative excess is a debt.
Take root = [0, 3, 0]. The left child holds 3 coins and is one node, so it returns 3 - 1 = +2. The right child holds 0 coins, so it returns 0 - 1 = -1. At the root, moves grows by |+2| + |-1| = 3. The root returns 0 + 2 + (-1) - 1 = 0, because the coins equal the nodes. The answer is 3: two coins leave the left child, one stays at the root and one goes on to the right child.
Why the sum of absolute excesses is the fewest moves
There is a lower bound for each edge. If the subtree below an edge has excess e, then at least |e| coins must cross that edge. A move crosses only one edge, so the fewest moves is at least the sum of |e| over all edges.
The bound can be reached. Settle the tree in postorder. When a node is handled, its children have already passed their surplus up or asked for coins, so the node can pass spare coins to a child that has a debt, or send them up. No coin has to cross an edge in both directions, and no edge is crossed more than |e| times. So the sum is not only a lower bound but also the answer.
This is why the code adds abs(left) + abs(right) to moves at each node. The two terms are the two child edges. Each edge is counted once, at its upper node.
A second example: root = [1, 0, 0, null, 3]. The node 3 returns +2. Its parent, the left child 0, adds |0| + |2| = 2 to moves and returns 0 + 0 + 2 - 1 = +1. The right child 0 returns -1. The root adds |+1| + |-1| = 2, so moves is 4. The root returns 0.
Two traps: where abs goes, and the minus 1
The first trap is abs in the return statement. The function must return the signed excess. A subtree that is short of coins must return a negative number. If the function returns abs(node.val + left + right - 1), a debt of 1 comes back as a surplus of 1, and the parent counts coins that do not exist. For root = [2, 0, 2, 0] the correct answer is 4, and the code with abs in the return gives 2. Take abs only when adding to moves.
The second trap is the - 1. Every node keeps one coin, so the excess of a subtree is its coins minus its nodes, and the node itself counts as one. A leaf with one coin must return 0. Without the - 1, that leaf returns 1, as if it had a spare coin. For root = [3, 0, 0] the correct answer is 2, and the code without - 1 gives 0.
A third trap is to return moves from the function. The function returns the excess, and moves is a separate counter kept outside, so it needs nonlocal. The answer is moves after the call on the root ends.
The cost, and how it differs from Tree DP with a bend
Each node is visited once, and each visit does constant work. The time is O(N). The space is O(H) for the recursion stack, where H is the height.
This card is built on Bottom-Up DFS. Each call returns one number for its subtree, and the parent combines the numbers of its children. The difference is that the number is signed, and the parent uses it twice: once to add a cost to moves for the edge below, and once to build its own number.
Compare with the card Tree DP (Max Path Sum / Diameter). There you look for one best path that may bend at any node, and the answer is stored in global_max. Here you add a cost for every edge, and the answer is the total in moves. Every edge is counted, not one chosen path.
Other problems on this card have the same shape: a subtree reports one number, and the parent pays a cost for the edge from that number. Minimum Fuel Cost to Report to the Capital (LC 2477) is one of them.
60-Second Pitch
"Coins can enter or leave a subtree only through the edge to its parent, so one signed number describes the subtree. excess(node) returns node.val + left + right - 1: the coins in the subtree minus its nodes. At each node I add abs(left) + abs(right) to moves, because that many coins must cross the two child edges. I take abs only for moves, and I return the signed value. Each edge needs at least its excess in crossings, and settling in postorder reaches exactly that, so the sum is the minimum. Each node is visited once, so the time is O(N). The space is O(H) for the recursion stack."
Common Questions
- Why is the sum of the absolute excesses the fewest moves?
Each edge has a subtree below it with a fixed excess
e. At least |e| coins must cross that edge, and each move crosses one edge. So the sum is a lower bound. Settling the tree from the leaves up reaches it, because no edge has to be crossed more than |e| times.- Why does the function return a signed number?
The sign tells the parent which way the coins go. A negative excess is a debt that the parent must cover. If the function returned the absolute value, the parent would treat the debt as spare coins and count coins that do not exist.
- What are the time and space complexity?
O(N)time, because each node is visited once with constant work. O(H) space for the recursion stack, whereHis the height.movesis one number.- Why is there a
- 1in the return value? Every node must end with one coin, so the node keeps one. The excess of a subtree is its coins minus its nodes. A leaf with one coin returns 0.
- How is this different from Binary Tree Maximum Path Sum (LC 124)?
Both return one number per subtree from a postorder walk. In Binary Tree Maximum Path Sum (LC 124), the answer is one best path that may bend at any node, and it is stored in
global_max. Here, the answer is a total over every edge, stored inmoves.
SWE Reasoning
Spot it
- Each subtree can be described by one number: what it holds minus what it needs.
- Things can enter or leave a subtree only through the edge to its parent.
- The wording asks for the fewest moves, trips or total cost over all edges, with every node ending in a fixed state.
- A greedy argument says that each edge must carry at least the subtree's imbalance, and the sum of these is reachable.
- The answer is a running total, not a single path.
The invariant, step by step
excess(node)returns the coins in the subtree atnodeminus the number of nodes in it.- A positive value means spare coins must leave through the edge above. A negative value means coins must enter. Either way, |excess| coins cross that edge.
- The base case is an empty subtree. It has no coins and no nodes, so it returns 0.
- At a node, the children report first.
moves += abs(left) + abs(right)counts the coins that cross the two child edges. - The node returns
node.val + left + right - 1, its own signed excess. The- 1is the coin the node keeps. - At the end,
movescounts every edge once with its minimum crossing. That is the fewest moves.
Traps
- Returning
abs(...). A debt becomes a surplus. Return the signed excess and takeabsonly when adding tomoves. - Forgetting
- 1. A node keeps one coin, so the excess is coins minus nodes. - Counting an edge twice. Add the child excess at the upper node only. Do not add it again at the child.
- Returning the excess instead of
moves. The answer is the total inmoves. The root's excess is 0. - Passing up the wrong value in related problems. In Make Costs of Paths Equal in a Binary Tree (LC 2673), the value passed up is the larger cost, not the smaller.