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•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

2D DP: Grid Paths (Unique Paths, Minimum Path Sum)

Grows into
Two-Sequence DP
Unlocks
Interval DPProbability DP

Unique Paths (LC 62)(opens leetcode.com)

Picture a robot dropped onto the top-left cell of an m x n grid, aiming for the bottom-right cell. On every move it can step either one cell down or one cell right -- nothing else.

Given the grid dimensions m and n, count how many distinct routes the robot could take from start to finish. You can assume the true count never exceeds 2 * 109.

Example 1
Input:m = 3, n = 7
Output:28
A robot moving only down or right has 28 different routes across a 3 x 7 grid.
000000000000000000000

Grid DP

grid dp[i][j] ➔ depends on the cell above and the cell to the left ➔ 1D rolling row
Spot it when: You move right and down through a grid, and the answer for a cell comes only from the cell above it and the cell to its left: count the paths, find the cheapest path, or grow the largest square.
Grid DP
Python
1def unique_paths(m: int, n: int) -> int:
2 """Unique Paths: count right/down paths in an m x n grid with one rolling row.
3 Time: O(M * N) | Space: O(N)
4 """
5 # row: one rolling row of the DP grid; row[j] starts at 1 since row 0 is reached only by moving right
6 row = [1] * n
7 for _ in range(1, m):
8 for j in range(1, n):
9 # 1. Combine Above And Left:
10 # row[j] still holds the count from the row above; add row[j - 1], already updated for this row.
11 row[j] += row[j - 1]
12 return row[n - 1]
Invariant: After row i is filled, row[j] is the answer for grid cell (i, j) for every j.

Give every grid cell (i, j) one answer, and build it only from the cell above and the cell to its left. Fill the grid row by row, so both cells are already final, and keep just one rolling row: row[j] still holds the answer from above when you add row[j - 1], the answer from the left. The trap is the first column: row[0] has no left neighbour and stays 1, so the inner loop must start at j = 1; starting at 0 reads row[-1], the last cell.

See it run

Unique Paths, step by step (3 by 3 grid)

row - · row[n - 1] -

row [1, 1, 1] · row[n - 1] 1

row [1, 1, 1] · row[n - 1] 1

row [1, 2, 1] · row[n - 1] 1

row [1, 2, 3] · row[n - 1] 3

row [1, 2, 3] · row[n - 1] 3

row [1, 3, 3] · row[n - 1] 3

row [1, 3, 6] · row[n - 1] 6

row [1, 3, 6] · row[n - 1] 6

Row i is the list row after grid row i is filled. Column j is the grid column j.

  • new the cell being written
  • above the cell one row up, which is read
  • left the cell one column to the left, which is read
  • answer the final answer

Start: unique_paths(3, 3) counts the right and down paths in a grid with 3 rows and 3 columns.

row = [1] * 3 is row 0. Each top cell is reached one way, by moving right.

Row 1: row[0] stays 1. It has no cell to its left. It is reached one way, by moving down.

Row 1, j = 1: row[1] += row[0]. The count above is 1 and the count to the left is 1. So row[1] becomes 2.

Row 1, j = 2: row[2] += row[1]. The count above is 1 and the count to the left is 2. So row[2] becomes 3.

Row 2: row[0] stays 1. It has no cell to its left. It is reached one way, by moving down.

Row 2, j = 1: row[1] += row[0]. The count above is 2 and the count to the left is 1. So row[1] becomes 3.

Row 2, j = 2: row[2] += row[1]. The count above is 3 and the count to the left is 3. So row[2] becomes 6.

The loops end. row[2] is 6, the number of paths to the bottom-right cell.

i = 0
j = 0
j = 1
j = 2
i = 1
i = 2
1 / 9
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Starting j at 0 in unique_paths

    Mistake
    for j in range(0, n):
    Fix
    for j in range(1, n):

    What happens: Column 0 is reached in exactly one way, so row[0] must stay 1. With j = 0, the line reads row[-1], which is the last cell. Python raises no error. On one column, row[0] += row[0] doubles the cell on every row.

    Example: m = 5, n = 1
    Mistake: returns 16Fix: returns 1
  2. 2

    Gotcha 2: Starting row as all zeros

    Mistake
    row = [0] * n
    Fix
    row = [1] * n

    What happens: Every cell of the first row is reached in exactly one way: by moving right along the top edge. A row of zeros says no path reaches any of them, so every count built from it is 0.

    Example: Unique Paths (LC 62): m = 3, n = 7
    Mistake: returns 0Fix: returns 28
  3. 3

    Gotcha 3: Filling the first row with 1 despite an obstacle

    Mistake
    row = [1] * n
    Fix
    row = [0] * n
    row[0] = 1

    What happens: In Unique Paths II (LC 63), a blocked cell has 0 ways. In the first row, a block also cuts off every cell to its right. row = [1] * n ignores that block. Start with only row[0] = 1, and run the obstacle check on the first row too.

    Example: Unique Paths II (LC 63): obstacleGrid = [[0, 1, 0], [0, 0, 0]]
    Mistake: returns 3Fix: returns 1
Not this one if…
  • Two strings are compared prefix by prefix, so a cell also looks at the cell diagonally up and to the left, as in Edit Distance (LC 72), and there is no grid to walk through. See Two-Sequence DP
  • The state is one range of a single sequence, built from shorter ranges inside it, as in Longest Palindromic Subsequence (LC 516), not a path from a corner of a grid. See Interval DP

Deep Dive

What one cell means

A grid DP gives every cell (i, j) one answer, and the answer is final once the cell is written. In unique_paths, row[j] after row i is the number of paths from the top-left corner to cell (i, j), when the walker moves only right or down.

The state is a pair of positions, so the number of states is the number of cells: M * N for a grid with M rows and N columns.

The same table shape works when the answer is not a count. In Minimum Path Sum (LC 64), a cell holds the smallest total cost of a path to it.

Why the recurrence is correct

A path that ends at cell (i, j) made its last move either down, from the cell above, or right, from the cell to the left. These two groups share no path, and together they contain every path. So the count is above + left, which the code writes as row[j] += row[j - 1].

The first row and the first column have only one neighbour. Each of their cells is reached in exactly one way, by moving along the edge. This is why row starts as [1] * n, and why the inner loop starts at j = 1.

Example: for m = 3 and n = 3, row is [1, 1, 1] at the start. After the second row it is [1, 2, 3]. After the third row it is [1, 3, 6], so unique_paths(3, 3) returns 6.

For Minimum Path Sum (LC 64), the sum is replaced by a minimum plus the cell's cost: row[j] = min(row[j], row[j - 1]) + cost. For [[1, 3, 1], [1, 5, 1], [4, 2, 1]] the answer is 7.

Fill order and why one row is enough

Cell (i, j) reads only the cell above and the cell to the left. Both are final before the cell is computed, if rows are filled from top to bottom and each row from left to right.

Only the previous row is ever read. So one array row is enough. When the loop reaches row[j], that cell still holds the value of the row above. The cell row[j - 1] was already updated for the current row. Their sum is the paths from above plus the paths from the left.

This is why the space is O(N), the width of one row, and not O(M * N) for the whole grid.

The cost, a formula, and what blocks it

Each cell is computed once with constant work. So the time is O(M * N). For a 1000 by 1000 grid, that is one million cell updates. The space is O(N).

For plain Unique Paths (LC 62) there is also a closed formula. Every path has m - 1 down moves and n - 1 right moves. The number of paths is the number of ways to choose where the down moves go, which is C(m + n - 2, m - 1). For m = 3 and n = 7 it is 28, the same as unique_paths(3, 7).

The formula stops working when a cell is blocked, as in Unique Paths II (LC 63), or when a cell has a cost, as in Minimum Path Sum (LC 64). The table handles both, so it is the general tool.

60-Second Pitch

"A brute force tries every path, which takes exponential time. I define one cell (i, j) as the answer for the walker that stops there. A path ends with a move down or a move right, so the cell depends only on the cell above and the cell to the left. The first row and column are reached one way each, so I start with a row of ones. Then I fill row by row, and I add row[j - 1] to row[j]. There are M times N cells with constant work each, so the time is O(M * N). I keep one row, so the space is O(N)."

Common Questions

What are the time and space complexity of unique_paths?

O(M * N) time, because each cell does constant work. O(N) space, because the code keeps one row of width N.

Why is a single row enough?

Each cell reads only the cell above and the cell to the left. When the loop reaches row[j], it still holds the value from the row above, and row[j - 1] was already updated for the current row. No older row is read.

How does the code change for obstacles, as in Unique Paths II (LC 63)?

A blocked cell has 0 paths, so the code sets row[j] = 0 for it. Start with row = [0] * n and row[0] = 1, and run the same check on the first row too. A block in the first row then cuts off the cells to its right.

How does the code change for Minimum Path Sum (LC 64)?

The cell holds the smallest cost of a path to it. The update becomes row[j] = min(row[j], row[j - 1]) + cost. The first row has only a left neighbour, and the first column has only an upper neighbour, so those cells copy the one neighbour they have. The time and space stay the same.

Is there a faster way for plain Unique Paths (LC 62)?

Yes. The answer is the binomial coefficient C(m + n - 2, m - 1), the number of ways to place the m - 1 down moves among all moves. For m = 3 and n = 7 it is 28. It works only for an empty grid. The table is needed for obstacles or costs.

SWE Reasoning

Spot it

  • A walker moves only right and down through a grid, from one corner to the opposite corner.
  • You count the paths, find the cheapest path, or find the largest square of cells.
  • The answer for a cell can be built from the cell above and the cell to its left.
  • The grid has up to about 100 or 1000 cells per side, so M * N cells is fine, and trying every path is exponential.
  • The problem asks for one number, not for the paths listed.

The invariant, step by step

  1. Before the loop, row = [1] * n. This is row 0. Each cell is reached one way, by moving right along the top edge.
  2. The outer loop moves to the next row. At this moment row[j] is the answer for the row above, for every j.
  3. Column 0 is not changed, because it has no left neighbour. It stays 1, as it is reached one way by moving down.
  4. For each j from 1 to n - 1, the code runs row[j] += row[j - 1]. row[j] is the count from above, and row[j - 1] is the count from the left, already updated for this row.
  5. After the inner loop, row[j] is the answer for cell (i, j) for every j, which is the card's invariant.
  6. After the last row, row[n - 1] is the answer for the bottom-right cell. The code returns it.

Traps

  • Start j at 0. row[j - 1] becomes row[-1], the last cell. Python raises no error. On one column, unique_paths(5, 1) returns 16 instead of 1.
  • Start row as zeros. The first row then says that no path reaches any cell, and every count stays 0. unique_paths(3, 7) returns 0 instead of 28.
  • Fill the first row with 1 despite an obstacle. In Unique Paths II (LC 63), a blocked cell has 0 paths, and it also cuts off every cell to its right in the first row. Start with only row[0] = 1, and apply the obstacle check to the first row too.
  • Use max or + in the wrong place. Counting paths adds the two neighbours. A cheapest path takes the smaller neighbour and then adds the cell's own cost.
  • Use the formula when cells are blocked or have costs. The binomial formula counts every path in an empty grid only.

Practice

Canonical LeetCode:Unique Paths (LC 62)Unique Paths II (LC 63)Minimum Path Sum (LC 64)Pascal's Triangle (LC 118)Maximal Square (LC 221)
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

2D DP: Grid Paths (Unique Paths, Minimum Path Sum)

Grows into
Two-Sequence DP
Unlocks
Interval DPProbability DP

Unique Paths (LC 62)(opens leetcode.com)

Picture a robot dropped onto the top-left cell of an m x n grid, aiming for the bottom-right cell. On every move it can step either one cell down or one cell right -- nothing else.

Given the grid dimensions m and n, count how many distinct routes the robot could take from start to finish. You can assume the true count never exceeds 2 * 109.

Example 1
Input:m = 3, n = 7
Output:28
A robot moving only down or right has 28 different routes across a 3 x 7 grid.
000000000000000000000

Grid DP

grid dp[i][j] ➔ depends on the cell above and the cell to the left ➔ 1D rolling row
Spot it when: You move right and down through a grid, and the answer for a cell comes only from the cell above it and the cell to its left: count the paths, find the cheapest path, or grow the largest square.
Grid DP
Python
1def unique_paths(m: int, n: int) -> int:
2 """Unique Paths: count right/down paths in an m x n grid with one rolling row.
3 Time: O(M * N) | Space: O(N)
4 """
5 # row: one rolling row of the DP grid; row[j] starts at 1 since row 0 is reached only by moving right
6 row = [1] * n
7 for _ in range(1, m):
8 for j in range(1, n):
9 # 1. Combine Above And Left:
10 # row[j] still holds the count from the row above; add row[j - 1], already updated for this row.
11 row[j] += row[j - 1]
12 return row[n - 1]
Invariant: After row i is filled, row[j] is the answer for grid cell (i, j) for every j.

Give every grid cell (i, j) one answer, and build it only from the cell above and the cell to its left. Fill the grid row by row, so both cells are already final, and keep just one rolling row: row[j] still holds the answer from above when you add row[j - 1], the answer from the left. The trap is the first column: row[0] has no left neighbour and stays 1, so the inner loop must start at j = 1; starting at 0 reads row[-1], the last cell.

See it run

Unique Paths, step by step (3 by 3 grid)

row - · row[n - 1] -

row [1, 1, 1] · row[n - 1] 1

row [1, 1, 1] · row[n - 1] 1

row [1, 2, 1] · row[n - 1] 1

row [1, 2, 3] · row[n - 1] 3

row [1, 2, 3] · row[n - 1] 3

row [1, 3, 3] · row[n - 1] 3

row [1, 3, 6] · row[n - 1] 6

row [1, 3, 6] · row[n - 1] 6

Row i is the list row after grid row i is filled. Column j is the grid column j.

  • new the cell being written
  • above the cell one row up, which is read
  • left the cell one column to the left, which is read
  • answer the final answer

Start: unique_paths(3, 3) counts the right and down paths in a grid with 3 rows and 3 columns.

row = [1] * 3 is row 0. Each top cell is reached one way, by moving right.

Row 1: row[0] stays 1. It has no cell to its left. It is reached one way, by moving down.

Row 1, j = 1: row[1] += row[0]. The count above is 1 and the count to the left is 1. So row[1] becomes 2.

Row 1, j = 2: row[2] += row[1]. The count above is 1 and the count to the left is 2. So row[2] becomes 3.

Row 2: row[0] stays 1. It has no cell to its left. It is reached one way, by moving down.

Row 2, j = 1: row[1] += row[0]. The count above is 2 and the count to the left is 1. So row[1] becomes 3.

Row 2, j = 2: row[2] += row[1]. The count above is 3 and the count to the left is 3. So row[2] becomes 6.

The loops end. row[2] is 6, the number of paths to the bottom-right cell.

i = 0
j = 0
j = 1
j = 2
i = 1
i = 2
1 / 9
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Starting j at 0 in unique_paths

    Mistake
    for j in range(0, n):
    Fix
    for j in range(1, n):

    What happens: Column 0 is reached in exactly one way, so row[0] must stay 1. With j = 0, the line reads row[-1], which is the last cell. Python raises no error. On one column, row[0] += row[0] doubles the cell on every row.

    Example: m = 5, n = 1
    Mistake: returns 16Fix: returns 1
  2. 2

    Gotcha 2: Starting row as all zeros

    Mistake
    row = [0] * n
    Fix
    row = [1] * n

    What happens: Every cell of the first row is reached in exactly one way: by moving right along the top edge. A row of zeros says no path reaches any of them, so every count built from it is 0.

    Example: Unique Paths (LC 62): m = 3, n = 7
    Mistake: returns 0Fix: returns 28
  3. 3

    Gotcha 3: Filling the first row with 1 despite an obstacle

    Mistake
    row = [1] * n
    Fix
    row = [0] * n
    row[0] = 1

    What happens: In Unique Paths II (LC 63), a blocked cell has 0 ways. In the first row, a block also cuts off every cell to its right. row = [1] * n ignores that block. Start with only row[0] = 1, and run the obstacle check on the first row too.

    Example: Unique Paths II (LC 63): obstacleGrid = [[0, 1, 0], [0, 0, 0]]
    Mistake: returns 3Fix: returns 1
Not this one if…
  • Two strings are compared prefix by prefix, so a cell also looks at the cell diagonally up and to the left, as in Edit Distance (LC 72), and there is no grid to walk through. See Two-Sequence DP
  • The state is one range of a single sequence, built from shorter ranges inside it, as in Longest Palindromic Subsequence (LC 516), not a path from a corner of a grid. See Interval DP

Deep Dive

What one cell means

A grid DP gives every cell (i, j) one answer, and the answer is final once the cell is written. In unique_paths, row[j] after row i is the number of paths from the top-left corner to cell (i, j), when the walker moves only right or down.

The state is a pair of positions, so the number of states is the number of cells: M * N for a grid with M rows and N columns.

The same table shape works when the answer is not a count. In Minimum Path Sum (LC 64), a cell holds the smallest total cost of a path to it.

Why the recurrence is correct

A path that ends at cell (i, j) made its last move either down, from the cell above, or right, from the cell to the left. These two groups share no path, and together they contain every path. So the count is above + left, which the code writes as row[j] += row[j - 1].

The first row and the first column have only one neighbour. Each of their cells is reached in exactly one way, by moving along the edge. This is why row starts as [1] * n, and why the inner loop starts at j = 1.

Example: for m = 3 and n = 3, row is [1, 1, 1] at the start. After the second row it is [1, 2, 3]. After the third row it is [1, 3, 6], so unique_paths(3, 3) returns 6.

For Minimum Path Sum (LC 64), the sum is replaced by a minimum plus the cell's cost: row[j] = min(row[j], row[j - 1]) + cost. For [[1, 3, 1], [1, 5, 1], [4, 2, 1]] the answer is 7.

Fill order and why one row is enough

Cell (i, j) reads only the cell above and the cell to the left. Both are final before the cell is computed, if rows are filled from top to bottom and each row from left to right.

Only the previous row is ever read. So one array row is enough. When the loop reaches row[j], that cell still holds the value of the row above. The cell row[j - 1] was already updated for the current row. Their sum is the paths from above plus the paths from the left.

This is why the space is O(N), the width of one row, and not O(M * N) for the whole grid.

The cost, a formula, and what blocks it

Each cell is computed once with constant work. So the time is O(M * N). For a 1000 by 1000 grid, that is one million cell updates. The space is O(N).

For plain Unique Paths (LC 62) there is also a closed formula. Every path has m - 1 down moves and n - 1 right moves. The number of paths is the number of ways to choose where the down moves go, which is C(m + n - 2, m - 1). For m = 3 and n = 7 it is 28, the same as unique_paths(3, 7).

The formula stops working when a cell is blocked, as in Unique Paths II (LC 63), or when a cell has a cost, as in Minimum Path Sum (LC 64). The table handles both, so it is the general tool.

60-Second Pitch

"A brute force tries every path, which takes exponential time. I define one cell (i, j) as the answer for the walker that stops there. A path ends with a move down or a move right, so the cell depends only on the cell above and the cell to the left. The first row and column are reached one way each, so I start with a row of ones. Then I fill row by row, and I add row[j - 1] to row[j]. There are M times N cells with constant work each, so the time is O(M * N). I keep one row, so the space is O(N)."

Common Questions

What are the time and space complexity of unique_paths?

O(M * N) time, because each cell does constant work. O(N) space, because the code keeps one row of width N.

Why is a single row enough?

Each cell reads only the cell above and the cell to the left. When the loop reaches row[j], it still holds the value from the row above, and row[j - 1] was already updated for the current row. No older row is read.

How does the code change for obstacles, as in Unique Paths II (LC 63)?

A blocked cell has 0 paths, so the code sets row[j] = 0 for it. Start with row = [0] * n and row[0] = 1, and run the same check on the first row too. A block in the first row then cuts off the cells to its right.

How does the code change for Minimum Path Sum (LC 64)?

The cell holds the smallest cost of a path to it. The update becomes row[j] = min(row[j], row[j - 1]) + cost. The first row has only a left neighbour, and the first column has only an upper neighbour, so those cells copy the one neighbour they have. The time and space stay the same.

Is there a faster way for plain Unique Paths (LC 62)?

Yes. The answer is the binomial coefficient C(m + n - 2, m - 1), the number of ways to place the m - 1 down moves among all moves. For m = 3 and n = 7 it is 28. It works only for an empty grid. The table is needed for obstacles or costs.

SWE Reasoning

Spot it

  • A walker moves only right and down through a grid, from one corner to the opposite corner.
  • You count the paths, find the cheapest path, or find the largest square of cells.
  • The answer for a cell can be built from the cell above and the cell to its left.
  • The grid has up to about 100 or 1000 cells per side, so M * N cells is fine, and trying every path is exponential.
  • The problem asks for one number, not for the paths listed.

The invariant, step by step

  1. Before the loop, row = [1] * n. This is row 0. Each cell is reached one way, by moving right along the top edge.
  2. The outer loop moves to the next row. At this moment row[j] is the answer for the row above, for every j.
  3. Column 0 is not changed, because it has no left neighbour. It stays 1, as it is reached one way by moving down.
  4. For each j from 1 to n - 1, the code runs row[j] += row[j - 1]. row[j] is the count from above, and row[j - 1] is the count from the left, already updated for this row.
  5. After the inner loop, row[j] is the answer for cell (i, j) for every j, which is the card's invariant.
  6. After the last row, row[n - 1] is the answer for the bottom-right cell. The code returns it.

Traps

  • Start j at 0. row[j - 1] becomes row[-1], the last cell. Python raises no error. On one column, unique_paths(5, 1) returns 16 instead of 1.
  • Start row as zeros. The first row then says that no path reaches any cell, and every count stays 0. unique_paths(3, 7) returns 0 instead of 28.
  • Fill the first row with 1 despite an obstacle. In Unique Paths II (LC 63), a blocked cell has 0 paths, and it also cuts off every cell to its right in the first row. Start with only row[0] = 1, and apply the obstacle check to the first row too.
  • Use max or + in the wrong place. Counting paths adds the two neighbours. A cheapest path takes the smaller neighbour and then adds the cell's own cost.
  • Use the formula when cells are blocked or have costs. The binomial formula counts every path in an empty grid only.

Practice

Canonical LeetCode:Unique Paths (LC 62)Unique Paths II (LC 63)Minimum Path Sum (LC 64)Pascal's Triangle (LC 118)Maximal Square (LC 221)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•