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)
MediumLC 1448

Count Good Nodes in Binary Tree (LeetCode 1448)

Target Frequency:MetaAmazonMicrosoft

You're given a binary tree root. Call a node X good when no node on the path from the root down to X has a value greater than X's own value.

Count how many nodes in the tree qualify as good.

Worked Examples

Example 1
Input:root = [3,1,4,3,null,1,5]
Output:4
313145
Explanation: Four nodes qualify: the root (3) is always good since nothing precedes it; node 4 is good because 4 is the largest value on its path (3, 4); node 5 is good because 5 is the largest on its path (3, 4, 5); and the rightmost node 3 is good because 3 ties the largest value on its path (3, 1, 3).
Example 2
Input:root = [3,3,null,4,2]
Output:3
4323
Explanation: Node 2 is not good: its path is 3 -> 3 -> 2, and 3 is greater than 2. The other three nodes are good.
Example 3
Input:root = [1]
Output:1
1
Explanation: The single root node always counts as good, since it has no ancestors to outrank it.

⚖️Formal Constraints & Bounds

  • The number of nodes in the binary tree is in the range [1, 105].

  • Each node's value is between [-104, 104].

Deep-Dive & Conceptual Insights

Core Insight

Carrying path maxima down the call stack provides O(1) local evaluation without back-tracking.

Real-World Scenario & Production Applications

Hierarchical access control validation (inheriting highest privilege level along an organizational tree).

Step-by-Step Execution Trace Table

NodePath Max on EntryNode.val >= Max?Is Good?Next Max for Children
3 (root)33 >= 3 (True)Yes (1)3
131 >= 3 (False)No (0)3
434 >= 3 (True)Yes (1)4
545 >= 4 (True)Yes (1)5
Trace Inputroot = [3,1,4,3,null,1,5]
Expected4
NodePath Max on EntryNode.val >= Max?Is Good?Next Max for Children
3 (root)33 >= 3 (True)Yes (1)3
131 >= 3 (False)No (0)3
434 >= 3 (True)Yes (1)4
545 >= 4 (True)Yes (1)5
Code Shape
Archetype Code Shape
Python
1def dfs(node, max_so_far):
2 if not node:
3 return 0
4 is_good = 1 if node.val >= max_so_far else 0
5 new_max = max(max_so_far, node.val)
6 return is_good + dfs(node.left, new_max) + dfs(node.right, new_max)
7return dfs(root, float('-inf'))

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

This archetype flips the direction of information flow from the ones before it: instead of children reporting answers up to their parent, the parent hands context down to its children.

🥾 The Analogy: Hiking and Tracking Your Personal Best Altitude

Imagine hiking down from a mountain's root, tracking only one number as you go: the highest altitude you've reached so far on this specific path. At every new point, you compare your current altitude to that personal-best. If you're at or above it, this point counts as "good" — nothing earlier on your path was ever higher — and your personal-best updates itself if you've just set a new record. You never need to remember the entire path behind you, only the single running maximum.

🔑 The Key Idea in the Code

max_so_far is a parameter passed down into each recursive call — the opposite direction from maxDepth's left/right values flowing up. Each node only ever needs the single running maximum handed to it by its parent; it never needs to inspect the full root-to-node path directly.

  • Mutating global max across sibling branches: Passing a mutable reference or global variable for max_val leaks the maximum seen in the left subtree into the right subtree traversal. Always pass max_val by value down the call stack.

  • Updating running max too late: If you evaluate node.val >= max_val, remember to update new_max = max(max_val, node.val) before passing to child recursions.

  • Strictly greater vs greater-or-equal: A node is 'good' if its value is greater than OR EQUAL to the maximum on the path from root. Using strict > drops valid nodes.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement calls a node good when nothing on the path from the root down to it is larger: whether a node counts depends only on the values above it, the Top-Down DFS signal.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

At each node: is_good = 1 if node.val >= max_so_far else 0, then both children get new_max = max(max_so_far, node.val), and the node returns is_good plus its children's counts.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The card's trap: start max_so_far at -inf (or root.val), never at 0, since values can be negative.

  • Compare with >=, not >: a node equal to the path maximum is good. On [3,3,null,4,2] the second 3 counts and the answer is 3; > returns 2.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use Top-Down DFS. Whether a node is good depends only on the values above it, so instead of looking back up the tree I carry the largest value seen so far down as an argument, max_so_far. At each node I compare: if its value is at least max_so_far, it's good. Then I pass the larger of the two to both children and add up the counts they return. Because the value travels as an argument, each call has its own copy, so the left subtree's changes never leak into the right one, and nothing needs undoing on the way back. The trap is the starting value: I start at minus infinity, not zero, because values can be negative and the root must always count. I also compare with greater-or-equal, since a tie with the path maximum is still good. Each node is visited once, so it's O(N) time and O(H) recursion stack.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the execution steps: Pre-order DFS passes down the maximum value seen along the path from root. If node.val >= max_so_far, increments good count and updates max_so_far. Each node visited once.

SPACE COMPLEXITY

O(H) recursion stack

Call stack depth equals tree height H.

Formal Recurrence Relation

T(N) = O(N)

Derivation Progression

Path Max Update

new_max = max(max_so_far, node.val)

Propagates maximum value down each branch in O(1) per frame.

Good Node Test

is_good = 1 if node.val >= max_so_far else 0

Tests good node condition in constant time.

Summation Across Tree

∑_{v} 1 = N ⟹ O(N)

Visits all N nodes once.

Variable Definitions

NNN

Total number of nodes in the binary tree

HHH

Tree height (log N in balanced tree, N in skewed tree)

Memory Architecture & Bounds

🟣 Call Stack

O(H) Call stack depth

🔵 Auxiliary Heap

O(1) Scalar counter

🟢 Output Space

O(1) Integer count

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N) time, O(log⁡N)O(\log N)O(logN) space.

Average Case

O(N)O(N)O(N) time, O(log⁡N)O(\log N)O(logN) space.

Worst Case

O(N)O(N)O(N) time, O(N)O(N)O(N) space.

Graph & Tree Traversal Frontier

Graph & Tree Traversal Frontier
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "count good nodes", "no nodes with a value greater than X on path from root". Whether a node counts depends on the path above it: Top-Down DFS, passing max_so_far down each branch.

CONSTRAINTS & BOUNDS

N≤105N \le 10^5N≤105, values in [−104,104][-10^4, 10^4][−104,104]. Time budget is O(N)O(N)O(N) linear time, space O(H)O(H)O(H) stack frames.

FAANG PRODUCTION TRAPS & EDGE CASES

LeetCode allows 10510^5105 nodes, so a chain is 10510^5105 calls deep, far past CPython's default recursion limit of 1000: walk with an explicit stack of (node, max_so_far) pairs instead.

Theory Context•Depth-First Search (DFS)
MediumLC 1448

Count Good Nodes in Binary Tree (LeetCode 1448)

Target Frequency:MetaAmazonMicrosoft

You're given a binary tree root. Call a node X good when no node on the path from the root down to X has a value greater than X's own value.

Count how many nodes in the tree qualify as good.

Worked Examples

Example 1
Input:root = [3,1,4,3,null,1,5]
Output:4
313145
Explanation: Four nodes qualify: the root (3) is always good since nothing precedes it; node 4 is good because 4 is the largest value on its path (3, 4); node 5 is good because 5 is the largest on its path (3, 4, 5); and the rightmost node 3 is good because 3 ties the largest value on its path (3, 1, 3).
Example 2
Input:root = [3,3,null,4,2]
Output:3
4323
Explanation: Node 2 is not good: its path is 3 -> 3 -> 2, and 3 is greater than 2. The other three nodes are good.
Example 3
Input:root = [1]
Output:1
1
Explanation: The single root node always counts as good, since it has no ancestors to outrank it.

⚖️Formal Constraints & Bounds

  • The number of nodes in the binary tree is in the range [1, 105].

  • Each node's value is between [-104, 104].

Deep-Dive & Conceptual Insights

Core Insight

Carrying path maxima down the call stack provides O(1) local evaluation without back-tracking.

Real-World Scenario & Production Applications

Hierarchical access control validation (inheriting highest privilege level along an organizational tree).

Step-by-Step Execution Trace Table

NodePath Max on EntryNode.val >= Max?Is Good?Next Max for Children
3 (root)33 >= 3 (True)Yes (1)3
131 >= 3 (False)No (0)3
434 >= 3 (True)Yes (1)4
545 >= 4 (True)Yes (1)5
Trace Inputroot = [3,1,4,3,null,1,5]
Expected4
NodePath Max on EntryNode.val >= Max?Is Good?Next Max for Children
3 (root)33 >= 3 (True)Yes (1)3
131 >= 3 (False)No (0)3
434 >= 3 (True)Yes (1)4
545 >= 4 (True)Yes (1)5
Code Shape
Archetype Code Shape
Python
1def dfs(node, max_so_far):
2 if not node:
3 return 0
4 is_good = 1 if node.val >= max_so_far else 0
5 new_max = max(max_so_far, node.val)
6 return is_good + dfs(node.left, new_max) + dfs(node.right, new_max)
7return dfs(root, float('-inf'))

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

This archetype flips the direction of information flow from the ones before it: instead of children reporting answers up to their parent, the parent hands context down to its children.

🥾 The Analogy: Hiking and Tracking Your Personal Best Altitude

Imagine hiking down from a mountain's root, tracking only one number as you go: the highest altitude you've reached so far on this specific path. At every new point, you compare your current altitude to that personal-best. If you're at or above it, this point counts as "good" — nothing earlier on your path was ever higher — and your personal-best updates itself if you've just set a new record. You never need to remember the entire path behind you, only the single running maximum.

🔑 The Key Idea in the Code

max_so_far is a parameter passed down into each recursive call — the opposite direction from maxDepth's left/right values flowing up. Each node only ever needs the single running maximum handed to it by its parent; it never needs to inspect the full root-to-node path directly.

  • Mutating global max across sibling branches: Passing a mutable reference or global variable for max_val leaks the maximum seen in the left subtree into the right subtree traversal. Always pass max_val by value down the call stack.

  • Updating running max too late: If you evaluate node.val >= max_val, remember to update new_max = max(max_val, node.val) before passing to child recursions.

  • Strictly greater vs greater-or-equal: A node is 'good' if its value is greater than OR EQUAL to the maximum on the path from root. Using strict > drops valid nodes.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement calls a node good when nothing on the path from the root down to it is larger: whether a node counts depends only on the values above it, the Top-Down DFS signal.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

At each node: is_good = 1 if node.val >= max_so_far else 0, then both children get new_max = max(max_so_far, node.val), and the node returns is_good plus its children's counts.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The card's trap: start max_so_far at -inf (or root.val), never at 0, since values can be negative.

  • Compare with >=, not >: a node equal to the path maximum is good. On [3,3,null,4,2] the second 3 counts and the answer is 3; > returns 2.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use Top-Down DFS. Whether a node is good depends only on the values above it, so instead of looking back up the tree I carry the largest value seen so far down as an argument, max_so_far. At each node I compare: if its value is at least max_so_far, it's good. Then I pass the larger of the two to both children and add up the counts they return. Because the value travels as an argument, each call has its own copy, so the left subtree's changes never leak into the right one, and nothing needs undoing on the way back. The trap is the starting value: I start at minus infinity, not zero, because values can be negative and the root must always count. I also compare with greater-or-equal, since a tie with the path maximum is still good. Each node is visited once, so it's O(N) time and O(H) recursion stack.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the execution steps: Pre-order DFS passes down the maximum value seen along the path from root. If node.val >= max_so_far, increments good count and updates max_so_far. Each node visited once.

SPACE COMPLEXITY

O(H) recursion stack

Call stack depth equals tree height H.

Formal Recurrence Relation

T(N) = O(N)

Derivation Progression

Path Max Update

new_max = max(max_so_far, node.val)

Propagates maximum value down each branch in O(1) per frame.

Good Node Test

is_good = 1 if node.val >= max_so_far else 0

Tests good node condition in constant time.

Summation Across Tree

∑_{v} 1 = N ⟹ O(N)

Visits all N nodes once.

Variable Definitions

NNN

Total number of nodes in the binary tree

HHH

Tree height (log N in balanced tree, N in skewed tree)

Memory Architecture & Bounds

🟣 Call Stack

O(H) Call stack depth

🔵 Auxiliary Heap

O(1) Scalar counter

🟢 Output Space

O(1) Integer count

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N) time, O(log⁡N)O(\log N)O(logN) space.

Average Case

O(N)O(N)O(N) time, O(log⁡N)O(\log N)O(logN) space.

Worst Case

O(N)O(N)O(N) time, O(N)O(N)O(N) space.

Graph & Tree Traversal Frontier

Graph & Tree Traversal Frontier
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "count good nodes", "no nodes with a value greater than X on path from root". Whether a node counts depends on the path above it: Top-Down DFS, passing max_so_far down each branch.

CONSTRAINTS & BOUNDS

N≤105N \le 10^5N≤105, values in [−104,104][-10^4, 10^4][−104,104]. Time budget is O(N)O(N)O(N) linear time, space O(H)O(H)O(H) stack frames.

FAANG PRODUCTION TRAPS & EDGE CASES

LeetCode allows 10510^5105 nodes, so a chain is 10510^5105 calls deep, far past CPython's default recursion limit of 1000: walk with an explicit stack of (node, max_so_far) pairs instead.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•