Topics › Dynamic Programming › 2D/3D DP and DP on Grids
Grid Unique Paths : DP on Grids (DP8)
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
A robot stands on the top-left cell (0, 0) of an m × n grid and wants to reach the bottom-right cell (m - 1, n - 1). At each step it can move only right or down. Count the distinct paths.
Constraints (LeetCode 62): 1 ≤ m, n ≤ 100, and the answer is guaranteed to be at most 2 × 10^9, so it fits in an int.
Example 1
m = 3, n = 7
answer = 28
Example 2
m = 3, n = 2
answer = 3 // down-down-right, down-right-down, right-down-down
Approach 1: Recursion
Intuition. This is the first DP on a grid, and the state is simply a cell. Let f(i, j) be the number of paths from (0, 0) to (i, j). The robot entered (i, j) with its last move, which was either a down move from (i - 1, j) or a right move from (i, j - 1). Those two groups of paths differ in their last move, so they do not overlap, and together they are all the paths. So
f(0, 0) = 1
f(i, j) = 0 if i < 0 or j < 0 (off the grid)
f(i, j) = f(i - 1, j) + f(i, j - 1) otherwise
The answer is f(m - 1, n - 1). Writing the recursion from the target back to the start makes the base case the fixed starting cell, which keeps it simple.
Algorithm.
- If
(i, j)is(0, 0), return 1. - If
i < 0orj < 0, return 0. - Return
f(i - 1, j) + f(i, j - 1).
static int recursive(int m, int n) {
return paths(m - 1, n - 1);
}
static int paths(int i, int j) {
if (i == 0 && j == 0) return 1; // reached the start
if (i < 0 || j < 0) return 0; // walked off the grid
return paths(i - 1, j) + paths(i, j - 1); // arrived from above or from the left
}
Complexity. Time O(2^(m+n)): every call branches twice and the depth is up to m + n - 2. Space O(m + n) for the recursion stack.
Approach 2: Memoization
Intuition. There are only m · n cells, but the recursion reaches most of them by many routes. For instance, f(1, 1) is requested from both f(2, 1) and f(1, 2). Cache each cell’s count in dp[i][j].
Algorithm.
- Create
dp[m][n]filled with-1. - After the two base cases, return
dp[i][j]if it is filled; otherwise compute it, store it, and return it.
static int memoization(int m, int n) {
int[][] dp = new int[m][n];
for (int[] row : dp) Arrays.fill(row, -1);
return memo(m - 1, n - 1, dp);
}
static int memo(int i, int j, int[][] dp) {
if (i == 0 && j == 0) return 1;
if (i < 0 || j < 0) return 0;
if (dp[i][j] != -1) return dp[i][j];
return dp[i][j] = memo(i - 1, j, dp) + memo(i, j - 1, dp);
}
Complexity. Time O(m · n): each cell is computed once. Space O(m · n) for the table plus O(m + n) stack.
Approach 3: Tabulation
Intuition. f(i, j) reads the cell above and the cell to the left. If we fill the grid row by row, left to right, both of those are always ready. Cells in row 0 have nothing above and cells in column 0 have nothing to their left; those terms count as 0, matching the “off the grid” base case.
Algorithm.
dp[0][0] = 1.- For every other cell in row-major order:
dp[i][j] = (i > 0 ? dp[i - 1][j] : 0) + (j > 0 ? dp[i][j - 1] : 0). - Return
dp[m - 1][n - 1].
static int tabulation(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) { dp[i][j] = 1; continue; }
int up = i > 0 ? dp[i - 1][j] : 0;
int left = j > 0 ? dp[i][j - 1] : 0;
dp[i][j] = up + left;
}
}
return dp[m - 1][n - 1];
}
Dry run for m = 3, n = 7:
| j=0 | j=1 | j=2 | j=3 | j=4 | j=5 | j=6 | |
|---|---|---|---|---|---|---|---|
| i=0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| i=1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| i=2 | 1 | 3 | 6 | 10 | 15 | 21 | 28 |
Each cell is the one above it plus the one to its left. The bottom-right cell is 28.
Complexity. Time O(m · n), space O(m · n).
Approach 4: Space optimized
Intuition. Row i needs only row i - 1. Better still, one array is enough. When we are about to update row[j] during row i, it still holds the value from row i - 1 (the cell above), and row[j - 1] has already been updated for row i (the cell to the left). So row[j] += row[j - 1] is exactly “above plus left”.
Algorithm.
row = [1, 0, 0, ..., 0].- For each of the
mrows, forjfrom 1 ton - 1:row[j] += row[j - 1]. - Return
row[n - 1].
On the first pass row becomes all ones, which is row 0 of the table. Column 0 never changes from 1, which is also correct: the only path down the first column is straight down.
static int spaceOptimized(int m, int n) {
int[] row = new int[n];
row[0] = 1;
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++) {
row[j] += row[j - 1]; // from above (old row[j]) + from the left (new row[j - 1])
}
}
return row[n - 1];
}
Complexity. Time O(m · n), space O(n).
Approach 5: Combinatorics
Intuition. Every path consists of exactly m - 1 down moves and n - 1 right moves, in some order, and every such order is a valid path that stays inside the grid. So counting paths means counting the ways to choose which m - 1 of the m + n - 2 moves are downs:
answer = C(m + n - 2, m - 1)
Computing the factorials directly would overflow long before the answer does. Instead, build the binomial coefficient one factor at a time. With N = m + n - 2 and r = min(m - 1, n - 1), after step i the running value equals C(N - r + i, i), which is always an integer. So the division at each step is exact, and the intermediate product never exceeds the final answer times N, which fits in long.
Algorithm.
N = m + n - 2,r = min(m - 1, n - 1),result = 1.- For
ifrom 1 tor:result = result · (N - r + i) / i. - Return
result.
static int combinatorics(int m, int n) {
int total = m + n - 2;
int r = Math.min(m - 1, n - 1);
long result = 1;
for (int i = 1; i <= r; i++) {
result = result * (total - r + i) / i; // result is now C(total - r + i, i): always an integer
}
return (int) result;
}
For m = 3, n = 7: N = 8, r = 2, and the steps give 1 · 7 / 1 = 7, then 7 · 8 / 2 = 28.
Complexity. Time O(min(m, n)), space O(1). This is the fastest method, but the DP versions are the ones that generalise: add obstacles, costs or forbidden moves, and the formula no longer applies while the table still does.
Edge cases and pitfalls
1 × 1grid. The robot is already at the target: exactly 1 path (the empty one), not 0.- Single row or column. Only one path exists, made entirely of right (or down) moves.
- The base case.
f(0, 0) = 1counts the single way to be at the start. If it returned 0, every count in the grid would be 0, because all paths are built from it. - Overflow in the formula. Never compute
(m + n - 2)!directly. Multiply and divide step by step inlong, and always divide right after multiplying. - Large grids. The tests include
51 × 9, whose answer1,916,797,311is close toInteger.MAX_VALUE. Every DP cell along the way is smaller than the final answer, sointis safe for inputs that respect the guarantee.
Related problems
- Grid unique paths is the same problem as it appears in the SDE sheet.
- Unique paths II adds blocked cells, where the formula fails but the DP adapts with one extra check.
- Minimum sum path in the matrix keeps the right/down moves and minimises a cost instead of counting.
- Triangle is a grid DP with a fixed start and a variable end.
- Climbing stairs uses the same “which move came last” counting argument in one dimension.