Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Tree Diameter DP, which you haven’t finished yet.

P3HardBuilt on Bottom-Up DFS~5 min speedrun

Greedy Postorder Tree DP (Distribute Coins, Minimum Fuel Cost)

Built on
Bottom-Up DFSEach subtree returns one signed number, what it holds minus what it needs, and the parent counts that many crossings of the edge above it.
The problem

Distribute Coins in Binary Tree (LC 979)(opens leetcode.com)

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.

Example 1
Input:root = [3,0,0]
Output:2
The root starts with three coins and sends one down to each of its two children, two moves in total.
030

Greedy Tree DP

Postorder Child State Code ➔ Greedy Parent Action
Spot it when: Each subtree can sum up what it needs or holds in one number, and you minimize what must cross the edges (coins, people, path costs, trips).
Greedy Tree DP
Python
1from typing import Optional
2 
3class 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 
9def 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
Invariant: 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 0 · answer not yet

returns - · moves 0 · answer not yet

returns +2 · moves 0 · answer not yet

returns - · moves 0 · answer not yet

returns -1 · moves 0 · answer not yet

returns - · moves 3 · answer not yet

returns 0 · moves 3 · answer not yet

returns - · moves 3 · answer 3

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 node the node being finished
  • excess a finished node, with the signed excess it returned
  • coins cross an 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.

Node 3 got 0 for the empty left side and 0 for the empty right side. Moves grows by |0| + |0| = 0, so moves = 0.

Node 3 returns 3 + 0 + 0 - 1 = 2. The subtree holds spare coins.

Node 0 got 0 for the empty left side and 0 for the empty right side. Moves grows by |0| + |0| = 0, so moves = 0.

Node 0 returns 0 + 0 + 0 - 1 = -1. The subtree needs coins from outside.

Node 0 got +2 from the left child and -1 from the right child. Moves grows by |2| + |-1| = 3, so moves = 3.

Node 0 returns 0 + 2 + (-1) - 1 = 0. The subtree holds exactly the coins it needs.

The recursion ended. The answer is moves = 3, not the root's excess.

300
excess returned (node→excess)
empty
1 / 8
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Returning abs(...) of the surplus instead of the signed surplus

    Mistake
    return abs(node.val + left + right - 1)
    Fix
    return node.val + left + right - 1

    What 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. Take abs only when adding to moves.

    Example: root = [2,0,2,0]
    Mistake: returns 2Fix: returns 4
  2. 2

    Gotcha 2: Forgetting to subtract 1 for the node itself

    Mistake
    return node.val + left + right
    Fix
    return node.val + left + right - 1

    What happens: Every node must keep one coin. So a subtree's surplus is its coins minus its nodes. A leaf with one coin has a surplus of 0. Without - 1, that leaf reports one spare coin. Every count of spare coins is then too large.

    Example: root = [3,0,0]
    Mistake: returns 0Fix: returns 2
  3. 3

    Gotcha 3: Passing up min(left, right) in Make Costs of Paths Equal

    Mistake
    return cost[i - 1] + min(left, right)
    Fix
    return cost[i - 1] + max(left, right)

    What happens: In Make Costs of Paths Equal in a Binary Tree (LC 2673), each subtree returns its largest root-to-leaf cost. The cheaper child is raised to match the other, and abs(left - right) is added to the answer. After that raise, both children cost max(left, right). Passing up min gives the parent a cost that no path has after the raise. The parent then adds the wrong difference.

    Example: n = 7, cost = [1,5,2,2,3,3,1]
    Mistake: returns 7Fix: returns 6
Not this one if…
  • 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, where H is the height. moves is one number.

Why is there a - 1 in 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 in moves.

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

  1. excess(node) returns the coins in the subtree at node minus the number of nodes in it.
  2. 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.
  3. The base case is an empty subtree. It has no coins and no nodes, so it returns 0.
  4. At a node, the children report first. moves += abs(left) + abs(right) counts the coins that cross the two child edges.
  5. The node returns node.val + left + right - 1, its own signed excess. The - 1 is the coin the node keeps.
  6. At the end, moves counts 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 take abs only when adding to moves.
  • 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 in moves. 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

Canonical LeetCode:Distribute Coins in Binary Tree (LC 979)Minimum Fuel Cost to Report to the Capital (LC 2477)Make Costs of Paths Equal in a Binary Tree (LC 2673)Minimum Time to Collect All Apples in a Tree (LC 1443)
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Tree Diameter DP, which you haven’t finished yet.

P3HardBuilt on Bottom-Up DFS~5 min speedrun

Greedy Postorder Tree DP (Distribute Coins, Minimum Fuel Cost)

Built on
Bottom-Up DFSEach subtree returns one signed number, what it holds minus what it needs, and the parent counts that many crossings of the edge above it.
The problem

Distribute Coins in Binary Tree (LC 979)(opens leetcode.com)

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.

Example 1
Input:root = [3,0,0]
Output:2
The root starts with three coins and sends one down to each of its two children, two moves in total.
030

Greedy Tree DP

Postorder Child State Code ➔ Greedy Parent Action
Spot it when: Each subtree can sum up what it needs or holds in one number, and you minimize what must cross the edges (coins, people, path costs, trips).
Greedy Tree DP
Python
1from typing import Optional
2 
3class 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 
9def 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
Invariant: 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 0 · answer not yet

returns - · moves 0 · answer not yet

returns +2 · moves 0 · answer not yet

returns - · moves 0 · answer not yet

returns -1 · moves 0 · answer not yet

returns - · moves 3 · answer not yet

returns 0 · moves 3 · answer not yet

returns - · moves 3 · answer 3

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 node the node being finished
  • excess a finished node, with the signed excess it returned
  • coins cross an 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.

Node 3 got 0 for the empty left side and 0 for the empty right side. Moves grows by |0| + |0| = 0, so moves = 0.

Node 3 returns 3 + 0 + 0 - 1 = 2. The subtree holds spare coins.

Node 0 got 0 for the empty left side and 0 for the empty right side. Moves grows by |0| + |0| = 0, so moves = 0.

Node 0 returns 0 + 0 + 0 - 1 = -1. The subtree needs coins from outside.

Node 0 got +2 from the left child and -1 from the right child. Moves grows by |2| + |-1| = 3, so moves = 3.

Node 0 returns 0 + 2 + (-1) - 1 = 0. The subtree holds exactly the coins it needs.

The recursion ended. The answer is moves = 3, not the root's excess.

300
excess returned (node→excess)
empty
1 / 8
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Returning abs(...) of the surplus instead of the signed surplus

    Mistake
    return abs(node.val + left + right - 1)
    Fix
    return node.val + left + right - 1

    What 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. Take abs only when adding to moves.

    Example: root = [2,0,2,0]
    Mistake: returns 2Fix: returns 4
  2. 2

    Gotcha 2: Forgetting to subtract 1 for the node itself

    Mistake
    return node.val + left + right
    Fix
    return node.val + left + right - 1

    What happens: Every node must keep one coin. So a subtree's surplus is its coins minus its nodes. A leaf with one coin has a surplus of 0. Without - 1, that leaf reports one spare coin. Every count of spare coins is then too large.

    Example: root = [3,0,0]
    Mistake: returns 0Fix: returns 2
  3. 3

    Gotcha 3: Passing up min(left, right) in Make Costs of Paths Equal

    Mistake
    return cost[i - 1] + min(left, right)
    Fix
    return cost[i - 1] + max(left, right)

    What happens: In Make Costs of Paths Equal in a Binary Tree (LC 2673), each subtree returns its largest root-to-leaf cost. The cheaper child is raised to match the other, and abs(left - right) is added to the answer. After that raise, both children cost max(left, right). Passing up min gives the parent a cost that no path has after the raise. The parent then adds the wrong difference.

    Example: n = 7, cost = [1,5,2,2,3,3,1]
    Mistake: returns 7Fix: returns 6
Not this one if…
  • 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, where H is the height. moves is one number.

Why is there a - 1 in 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 in moves.

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

  1. excess(node) returns the coins in the subtree at node minus the number of nodes in it.
  2. 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.
  3. The base case is an empty subtree. It has no coins and no nodes, so it returns 0.
  4. At a node, the children report first. moves += abs(left) + abs(right) counts the coins that cross the two child edges.
  5. The node returns node.val + left + right - 1, its own signed excess. The - 1 is the coin the node keeps.
  6. At the end, moves counts 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 take abs only when adding to moves.
  • 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 in moves. 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

Canonical LeetCode:Distribute Coins in Binary Tree (LC 979)Minimum Fuel Cost to Report to the Capital (LC 2477)Make Costs of Paths Equal in a Binary Tree (LC 2673)Minimum Time to Collect All Apples in a Tree (LC 1443)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•