Unique Paths
A medium problem from 2-D Dynamic Programming. Try it first, open a hint when you are stuck, and read the solution when you are done.
- Medium
- 2-D Dynamic Programming
The problem
A grid has m rows and n columns. You start in the top-left cell and want to reach the bottom-right cell. Each move takes you one cell to the right or one cell down. Return the number of different paths. Two paths are different when they make their moves in a different order. In a grid with a single cell you are already there, and that counts as one path.
What to write
function solve(m: number, n: number): numberExample 1
Input m = 3 n = 7 Output 28
With 3 rows and 7 columns, every path makes 2 moves down and 6 moves right, 8 in all. A path is fixed by which 2 of its 8 moves go down. Picking one of 8 and then one of the other 7 counts each pair twice, so there are 8 × 7 / 2 = 28 paths.
Example 2
Input m = 3 n = 2 Output 3
With 3 rows and 2 columns, every path makes 2 moves down and 1 move right. The single move right can come first, second or last, so there are 3 paths: right, down, down; down, right, down; and down, down, right.
Constraints
Integer m and n from 1 to 15.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Use the constraints
Every path makes the same moves in some order:
m- 1 moves down andn- 1 moves right. Only the order differs. Following the paths one by one takes as many walks as there are paths, and a 15 by 15 grid already has 40116600 of them.Hint 2: Ask a smaller question
Take a grid with 2 rows and 3 columns and count its paths by hand. Now think about the last move of a path: which cells can that move come from? How many paths lead to each of those cells?
Hint 3: Name the pattern
A path enters a cell from the cell above it or from the cell on its left, so the cell's number of paths is the sum of those two numbers. Working out an answer for every cell from answers already found, row by row, is two-dimensional dynamic programming. The signal: each cell depends only on earlier cells. Read the lesson on two-dimensional dynamic programming.
Hint 4: State the invariant
Keep one row of counts. When a grid row is finished, each slot holds the number of paths from the top-left cell to that cell of the row. While you fill the next row, slots on your left already belong to the new row, and your own slot still holds the count from the row above.
Hint 5: Sketch the pseudocode
row = a list of
nones. Repeatm- 1 times: for each position c from the second to the last, row[c] = row[c] + row[c - 1]. Return the last number in row.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Approach
Every cell receives paths from above and from the left. Initialize the first row to ones and update a single row of accumulated counts.
The slow way
From each cell, try the move right and the move down, and count 1 every time you arrive at the bottom-right cell. It is correct, but it walks every path separately. The 3 by 7 grid takes 119 cell visits for its 28 paths, and a 15 by 15 grid has 40116600 paths.
The insight
Many paths share their first moves, so walking each path separately repeats the same work. One number per cell is enough: how many paths reach it. A path's last move into a cell is down or right, so that number is the count for the cell above plus the count for the cell on the left. Reusing answers to smaller questions like this is called dynamic programming.
Step by step
- Start with the top row: one path to each of its cells. A cell in the top row can only be reached by moving right all the way, so there is exactly one path to it. Only one row of counts is kept, and it starts as the top row.
- Take each lower row in turn, from its second cell to its last. A row is built from the row above it, so the rows go top to bottom. The first cell of a row is reached only by moving straight down, so that cell keeps its 1 and is skipped.
- Add the count on the left to the count kept from the row above. Before the update, the slot still holds the count for the cell above. The slot on its left is already finished for this row. Their sum is the count for this cell.
- Return the last count: it belongs to the bottom-right cell. After the last pass the kept row is the bottom row of the grid, and its last slot is the bottom-right cell. With one row, or one column, no addition ever runs and the answer is the starting 1.
The same numbered steps are the comments in each solution.
JavaScript
function solve(m, n) {
// 1. Start with the top row: one path to each of its cells
const dp = Array(n).fill(1);
// 2. Take each lower row in turn, from its second cell to its last
for (let r = 1; r < m; r++)
for (let c = 1; c < n; c++)
// 3. Add the count on the left to the count kept from the row above
dp[c] += dp[c - 1];
// 4. Return the last count: it belongs to the bottom-right cell
return dp[n - 1];
}TypeScript
function solve(m: number, n: number): number {
// 1. Start with the top row: one path to each of its cells
const dp = Array(n).fill(1);
// 2. Take each lower row in turn, from its second cell to its last
for (let r = 1; r < m; r++)
for (let c = 1; c < n; c++)
// 3. Add the count on the left to the count kept from the row above
dp[c] += dp[c - 1];
// 4. Return the last count: it belongs to the bottom-right cell
return dp[n - 1];
}
Python
def solve(m, n):
# 1. Start with the top row: one path to each of its cells
dp = [1] * n
# 2. Take each lower row in turn, from its second cell to its last
for _ in range(1, m):
for col in range(1, n):
# 3. Add the count on the left to the count kept from the row above
dp[col] += dp[col - 1]
# 4. Return the last count: it belongs to the bottom-right cell
return dp[-1]
Example 1, traced
Input m = 3 n = 7 Output 28
| Cell being filled | Count from above | Count on the left | Their sum | Row kept afterwards |
|---|---|---|---|---|
| Top row, all 7 cells | - | - | - | [1, 1, 1, 1, 1, 1, 1] |
| Row 2, columns 2 to 7 | 1 each time | 1, 2, 3, 4, 5, 6 | 2, 3, 4, 5, 6, 7 | [1, 2, 3, 4, 5, 6, 7] |
| Row 3, column 2 | 2 | 1 | 3 | [1, 3, 3, 4, 5, 6, 7] |
| Row 3, column 3 | 3 | 3 | 6 | [1, 3, 6, 4, 5, 6, 7] |
| Row 3, column 4 | 4 | 6 | 10 | [1, 3, 6, 10, 5, 6, 7] |
| Row 3, column 5 | 5 | 10 | 15 | [1, 3, 6, 10, 15, 6, 7] |
| Row 3, column 6 | 6 | 15 | 21 | [1, 3, 6, 10, 15, 21, 7] |
| Row 3, column 7 | 7 | 21 | 28 | [1, 3, 6, 10, 15, 21, 28] |
Why it works
The top row and the first cell of every row are correct: one path each. Every other cell's paths split into two groups that share no path: those whose last move was down and those whose last move was to the right. If the counts above and on the left are correct, so is their sum, and both are always computed first.
Time and space
Time O(m × n), extra space O(n). Each of the m - 1 lower rows updates n - 1 slots with one addition each, so the time is O(m × n). A row needs only the row above it, so one row of n counts is kept and the space is O(n).
n and m are input sizes, h is a tree's height, V and E are a graph's vertices and edges. The bounds describe this reference solution.
Watch out
- The top row is already filled, so only
m- 1 rows are left. Running the row loopmtimes computes one row too many: the 3 by 7 grid returns 84 instead of 28. - Fill each row from left to right. Going right to left reads the left slot before that slot is updated, so it still belongs to the row above, and the 3 by 7 grid returns 4.
- Start the inner loop at the second cell. At the first cell, Python reads the last slot of the row and JavaScript reads undefined: the 3 by 7 grid gives 43 in Python and NaN in JavaScript.
Pattern
Two-dimensional dynamic programming. Builds on Climbing Stairs.