Unique Paths
Table of Contents + β
Unique Paths is the classic grid problem. The interviewer uses it to check if you can turn a movement puzzle into a clean grid of numbers. The trick is small. Every cell is just the sum of two neighbors. Once you see that, you are done.
π― The Problem
You have a grid and a robot moving through it. Here are the rules.
- The grid has some rows and columns.
- The robot starts in the top-left corner.
- The robot wants to reach the bottom-right corner.
- It can only step right or step down. No other move.
- You return the count of different paths that reach the goal.
For a grid of 3 rows by 7 columns, there are many ways to weave right and down. The answer is 28.
Input: m = 3, n = 7Output: 28
Explanation: The robot can only move right or down,and there are 28 distinct routes from top-left to bottom-right.Here is the movement. The robot starts at the top-left and only goes right or down.
π’ Approach 1: Plain Recursion (Brute Force)
The idea in one line: let the robot try every route and count the ones that reach the goal.
The idea:
- From any cell, the robot can go right or go down.
- Paths from a cell equal paths from the right cell plus paths from the cell below.
How it works:
- Write a function βhow many paths from row r, column c to the end?β
- It calls itself on the right cell and the cell below, then adds the two answers.
- Reaching the goal cell is one finished path, so return one.
- Stepping off the grid is a dead path, so return zero.
Why it is weak:
- It re-walks the same cells again and again from different routes.
- The time grows exponentially.
Here is the plain recursion code:
def unique_paths(m, n): def dfs(row, col): if row == m - 1 and col == n - 1: return 1 if row == m or col == n: return 0 return dfs(row + 1, col) + dfs(row, col + 1)
return dfs(0, 0)β‘ Approach 2: Add Memory With Memoization (Better)
The idea in one line: a cellβs path count never changes, so compute it once and store it.
The idea:
- The number of paths from cell
(r, c)to the end is always the same. - The recursion keeps re-solving the same cell.
How it works:
- Keep a 2D table the same shape as the grid.
- The first time we find a cellβs path count, we save it.
- Next time that cell comes up, we read the saved value.
- This is memoization, caching each cellβs answer so it is computed once.
Why it is faster:
- Each cell is solved once.
- The time drops to O(m times n).
Here is the memoized recursion:
from functools import lru_cache
def unique_paths(m, n): @lru_cache(None) def dfs(row, col): if row == m - 1 and col == n - 1: return 1 if row == m or col == n: return 0 return dfs(row + 1, col) + dfs(row, col + 1)
return dfs(0, 0)β‘ Approach 3: Bottom-Up Tabulation (Better)
The idea in one line: flip the direction and fill a grid from the top-left outward.
The idea:
- Instead of recursing toward the goal, build out from the start.
- Make a 2D grid
dpwheredp[r][c]holds the paths to reach that cell.
How it works:
- The first row has one path each. The robot can only come from the left.
- The first column has one path each. It can only come from above.
- Every other cell is the cell above plus the cell to its left.
- Tabulation fills the grid in order so each cell has its two neighbors ready.
Why it is solid:
- Each of the m times n cells is filled once.
- No recursion stack to worry about.
Here is the 2D grid filling up for a small 3 by 3 case. Each cell is the count of paths to reach it.
π Approach 4: Space-Optimized One Row (Best)
The idea in one line: each cell needs only the cell above and the cell to its left, so keep just one row.
The idea:
- The full grid is not needed in memory.
- We keep a single row and roll it down.
How it works:
- Process the grid row by row.
- The value already sitting in a slot is the cell from the row above.
- The value just to the left in the same row is the left neighbor.
- Add the slotβs old value to the left value, and store it back.
Why it is best:
- Just as fast as the 2D table.
- The memory drops from O(m times n) to O(n).
Steps to Solve
- Make a single array
dpof lengthn. Fill it all with1, since the first row has one path per cell. - Loop over each remaining row, from the second row to the last.
- Inside each row, loop over the columns from the second column to the last.
- Set
dp[c]todp[c]plusdp[c - 1]. The olddp[c]is the cell above, anddp[c - 1]is the cell to the left. - The first column stays
1the whole time, since there is only one way down the edge. - After the last row,
dp[n - 1]holds the total number of paths.
This Python version keeps one list of path counts and adds the left value into each cell.
def unique_paths(m, n): dp = [1] * n # first row: one path each
for r in range(1, m): for c in range(1, n): dp[c] = dp[c] + dp[c - 1] # above + left return dp[n - 1]
m, n = 3, 7print(unique_paths(m, n))The output of the above code will be:
28Let us walk through the Python version line by line. The why behind each line is what makes the grid click.
dp = [1] * n makes one row of counts, all set to one. This stands for the very first grid row. The robot can only slide right along the top, so every top cell has exactly one path.
for r in range(1, m): moves down the grid one row at a time. We start at row one, not row zero, because row zero is already filled with ones.
for c in range(1, n): walks across the columns, starting at column one. Column zero stays one the whole time, since the only way down the left edge is straight down.
dp[c] = dp[c] + dp[c - 1] is the transition, and it carries the whole idea. The old dp[c] still holds the value from the row above, because we have not overwritten it yet. The dp[c - 1] is the cell just to the left in this same row. Adding them gives the paths into this cell.
return dp[n - 1] gives the answer. After the last row, the final slot holds the count of all paths to the bottom-right corner.
β±οΈ Time and Space Complexity
Plain recursion is exponential, since it re-walks the same cells over and over. Memoization and 2D tabulation both visit each of the m times n cells once, so they run in O(m times n) time. The space-optimized version keeps a single row, so it uses the least memory while staying just as fast.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Plain recursion | O(2^(m+n)) | O(m + n) |
| Memoization | O(m Γ n) | O(m Γ n) |
| Tabulation (2D grid) | O(m Γ n) | O(m Γ n) |
| Space-optimized (one row) | O(m Γ n) | O(n) |
Tip
The whole problem hides in one rule: each cell equals the cell above plus the cell to its left. If you can state that rule clearly, the code almost writes itself.
π§© Key Takeaways
- β The robot can only move right or down, which keeps the choices simple.
- β Each cell holds the number of paths to reach it from the start.
- β The transition is just cell above plus cell to the left.
- β The first row and first column are all ones, since there is one way along each edge.
- β One rolling row replaces the full grid and saves memory.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Which moves can the robot make in Unique Paths?
Why: The robot may only step right or step down, which is what makes the path count clean.
- 2
What value does dp[r][c] hold in the tabulation grid?
Why: Each cell stores how many distinct routes reach it from the top-left corner.
- 3
What is the transition rule for an inner cell?
Why: A cell can be reached from above or from the left, so its count is the sum of those two cells.
- 4
Why are all cells in the first row and first column equal to 1?
Why: Along the top edge the robot can only go right, and down the left edge it can only go down, so one path each.