There is an m x n grid where you are allowed to move either down or to the right at any point in time.
Given the two integers m and n, return the number of possible unique paths that can be taken from the top-left corner of the grid (grid[0][0]) to the bottom-right corner (grid[m - 1][n - 1]).
You may assume the output will fit in a 32-bit integer.
You should aim for a solution as good or better than O(m * n) time and O(m * n) space, where m is the number of rows and n is the number of columns in the grid.
Hint 1
Try to think in terms of recursion and visualize it as a decision tree, where we have two choices at each step. Can you determine the base condition and recurrence relation?
Hint 2
We recursively traverse the grid using row i and column j. At each step, we explore both possibilities: moving down or moving right, ensuring we do not go out of bounds. If we reach the bottom-right cell, we return 1.
Hint 3
This approach has exponential complexity. Can you think of a way to optimize the recursion? Maybe you should consider using a dynamic programming approach.
Hint 4
We can use memoization to cache the results of recursive calls and avoid recalculations. A hash map or a 2D array can be used to store these results.
classSolution:defuniquePaths(self, m:int, n:int)->int:defdfs(i, j):if i ==(m -1)and j ==(n -1):return1if i >= m or j >= n:return0return dfs(i, j +1)+ dfs(i +1, j)return dfs(0,0)
publicclassSolution{publicintuniquePaths(int m,int n){returndfs(0,0, m, n);}publicintdfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;returndfs(i, j +1, m, n)+dfs(i +1, j, m, n);}}
classSolution{public:intuniquePaths(int m,int n){returndfs(0,0, m, n);}intdfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;returndfs(i, j +1, m, n)+dfs(i +1, j, m, n);}};
classSolution{/**
* @param {number} m
* @param {number} n
* @return {number}
*/uniquePaths(m, n){constdfs=(i, j)=>{if(i == m -1&& j == n -1){return1;}if(i >= m || j >= n)return0;returndfs(i, j +1)+dfs(i +1, j);};returndfs(0,0);}}
publicclassSolution{publicintUniquePaths(int m,int n){returnDfs(0,0, m, n);}intDfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;returnDfs(i, j +1, m, n)+Dfs(i +1, j, m, n);}}
funcuniquePaths(m int, n int)int{var dfs func(i, j int)int
dfs =func(i, j int)int{if i == m-1&& j == n-1{return1}if i >= m || j >= n {return0}returndfs(i, j+1)+dfs(i+1, j)}returndfs(0,0)}
class Solution {fununiquePaths(m: Int, n: Int): Int {fundfs(i: Int, j: Int): Int {if(i == m -1&& j == n -1)return1if(i >= m || j >= n)return0returndfs(i, j +1)+dfs(i +1, j)}returndfs(0,0)}}
classSolution{funcuniquePaths(_ m:Int,_ n:Int)->Int{funcdfs(_ i:Int,_ j:Int)->Int{if i == m -1&& j == n -1{return1}if i >= m || j >= n {return0}returndfs(i, j +1)+dfs(i +1, j)}returndfs(0,0)}}
implSolution{pubfnunique_paths(m:i32, n:i32)->i32{fndfs(i:i32, j:i32, m:i32, n:i32)->i32{if i == m -1&& j == n -1{return1;}if i >= m || j >= n {return0;}dfs(i, j +1, m, n)+dfs(i +1, j, m, n)}dfs(0,0, m, n)}}
Time & Space Complexity
Time complexity: O(2m+n)
Space complexity: O(m+n)
Where m is the number of rows and n is the number of columns.
2. Dynamic Programming (Top-Down)
Intuition
This is the optimized version of recursion using memoization.
In the brute-force approach, the same cell (i, j) is solved many times. But the number of paths from a cell never changes, so we can store it once and reuse it.
Think of it this way:
Every cell (i, j) asks: “How many ways can I reach the destination from here?”
Once answered, we cache it so we never recompute it.
This turns an exponential recursion into a polynomial-time solution.
Algorithm
Create a 2D memo table memo[m][n], initialized to -1.
Define a recursive function dfs(i, j):
If (i, j) is the destination (m-1, n-1), return 1.
If (i, j) is out of bounds, return 0.
If memo[i][j] is already computed, return it.
Otherwise:
Compute paths by moving right and down: memo[i][j] = dfs(i, j+1) + dfs(i+1, j)
classSolution:defuniquePaths(self, m:int, n:int)->int:
memo =[[-1]* n for _ inrange(m)]defdfs(i, j):if i ==(m -1)and j ==(n -1):return1if i >= m or j >= n:return0if memo[i][j]!=-1:return memo[i][j]
memo[i][j]= dfs(i, j +1)+ dfs(i +1, j)return memo[i][j]return dfs(0,0)
publicclassSolution{int[][] memo;publicintuniquePaths(int m,int n){
memo =newint[m][n];for(int[] it : memo){Arrays.fill(it,-1);}returndfs(0,0, m, n);}publicintdfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;if(memo[i][j]!=-1){return memo[i][j];}return memo[i][j]=dfs(i, j +1, m, n)+dfs(i +1, j, m, n);}}
classSolution{public:
vector<vector<int>> memo;intuniquePaths(int m,int n){
memo.resize(m,vector<int>(n,-1));returndfs(0,0, m, n);}intdfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;if(memo[i][j]!=-1){return memo[i][j];}return memo[i][j]=dfs(i, j +1, m, n)+dfs(i +1, j, m, n);}};
classSolution{/**
* @param {number} m
* @param {number} n
* @return {number}
*/uniquePaths(m, n){const memo = Array.from({length: m },()=>Array(n).fill(-1));constdfs=(i, j)=>{if(i == m -1&& j == n -1){return1;}if(i >= m || j >= n)return0;if(memo[i][j]!=-1){return memo[i][j];}
memo[i][j]=dfs(i, j +1)+dfs(i +1, j);return memo[i][j];};returndfs(0,0);}}
publicclassSolution{int[,] memo;publicintUniquePaths(int m,int n){
memo =newint[m, n];for(int i =0; i < m; i++)for(int j =0; j < n; j++)
memo[i, j]=-1;returnDfs(0,0, m, n);}intDfs(int i,int j,int m,int n){if(i ==(m -1)&& j ==(n -1)){return1;}if(i >= m || j >= n)return0;if(memo[i, j]!=-1){return memo[i, j];}return memo[i, j]=Dfs(i, j +1, m, n)+Dfs(i +1, j, m, n);}}
funcuniquePaths(m int, n int)int{
memo :=make([][]int, m)for i :=range memo {
memo[i]=make([]int, n)for j :=range memo[i]{
memo[i][j]=-1}}var dfs func(i, j int)int
dfs =func(i, j int)int{if i == m-1&& j == n-1{return1}if i >= m || j >= n {return0}if memo[i][j]!=-1{return memo[i][j]}
memo[i][j]=dfs(i, j+1)+dfs(i+1, j)return memo[i][j]}returndfs(0,0)}
class Solution {fununiquePaths(m: Int, n: Int): Int {val memo =Array(m){IntArray(n){-1}}fundfs(i: Int, j: Int): Int {if(i == m -1&& j == n -1)return1if(i >= m || j >= n)return0if(memo[i][j]!=-1)return memo[i][j]
memo[i][j]=dfs(i, j +1)+dfs(i +1, j)return memo[i][j]}returndfs(0,0)}}
classSolution{funcuniquePaths(_ m:Int,_ n:Int)->Int{var memo =Array(repeating:Array(repeating:-1, count: n), count: m)funcdfs(_ i:Int,_ j:Int)->Int{if i == m -1&& j == n -1{return1}if i >= m || j >= n {return0}if memo[i][j]!=-1{return memo[i][j]}
memo[i][j]=dfs(i, j +1)+dfs(i +1, j)return memo[i][j]}returndfs(0,0)}}
implSolution{pubfnunique_paths(m:i32, n:i32)->i32{let(m, n)=(m asusize, n asusize);letmut memo =vec![vec![-1i32; n]; m];fndfs(i:usize, j:usize, m:usize, n:usize, memo:&mutVec<Vec<i32>>)->i32{if i == m -1&& j == n -1{return1;}if i >= m || j >= n {return0;}if memo[i][j]!=-1{return memo[i][j];}
memo[i][j]=dfs(i, j +1, m, n, memo)+dfs(i +1, j, m, n, memo);
memo[i][j]}dfs(0,0, m, n,&mut memo)}}
Time & Space Complexity
Time complexity: O(m∗n)
Space complexity: O(m∗n)
Where m is the number of rows and n is the number of columns.
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of starting from the top and recursing, we build the answer from the destination backward.
From any cell (i, j), the number of unique paths to the destination is:
paths from the cell below(i+1, j)
plus paths from the cell to the right(i, j+1)
If we already know these values, we can compute the current cell directly.
So we:
Set the destination cell to 1
Fill the grid bottom-up, right-to-left
Algorithm
Create a (m+1) x (n+1) DP table initialized with 0.
Set dp[m-1][n-1] = 1 (only one way to stay at destination).
Traverse rows from m-1 to 0 and columns from n-1 to 0.
For each cell (i, j): dp[i][j] = dp[i+1][j] + dp[i][j+1]
classSolution:defuniquePaths(self, m:int, n:int)->int:
row =[1]* n
for i inrange(m -1):
newRow =[1]* n
for j inrange(n -2,-1,-1):
newRow[j]= newRow[j +1]+ row[j]
row = newRow
return row[0]
publicclassSolution{publicintuniquePaths(int m,int n){int[] row =newint[n];Arrays.fill(row,1);for(int i =0; i < m -1; i++){int[] newRow =newint[n];Arrays.fill(newRow,1);for(int j = n -2; j >=0; j--){
newRow[j]= newRow[j +1]+ row[j];}
row = newRow;}return row[0];}}
classSolution{public:intuniquePaths(int m,int n){
vector<int>row(n,1);for(int i =0; i < m -1;++i){
vector<int>newRow(n,1);for(int j = n -2; j >=0;--j){
newRow[j]= newRow[j +1]+ row[j];}
row = newRow;}return row[0];}};
classSolution{/**
* @param {number} m
* @param {number} n
* @return {number}
*/uniquePaths(m, n){let row =newArray(n).fill(1);for(let i =0; i < m -1; i++){const newRow =newArray(n).fill(1);for(let j = n -2; j >=0; j--){
newRow[j]= newRow[j +1]+ row[j];}
row = newRow;}return row[0];}}
publicclassSolution{publicintUniquePaths(int m,int n){var row =newint[n];
Array.Fill(row,1);for(int i =0; i < m -1; i++){var newRow =newint[n];
Array.Fill(newRow,1);for(int j = n -2; j >=0; j--){
newRow[j]= newRow[j +1]+ row[j];}
row = newRow;}return row[0];}}
funcuniquePaths(m int, n int)int{
row :=make([]int, n)for i :=range row {
row[i]=1}for i :=0; i < m -1; i++{
newRow :=make([]int, n)
newRow[n-1]=1for j := n -2; j >=0; j--{
newRow[j]= newRow[j+1]+ row[j]}
row = newRow
}return row[0]}
class Solution {fununiquePaths(m: Int, n: Int): Int {var row =IntArray(n){1}for(i in0 until m -1){val newRow =IntArray(n){1}for(j in n -2 downTo 0){
newRow[j]= newRow[j +1]+ row[j]}
row = newRow
}return row[0]}}
classSolution:defuniquePaths(self, m:int, n:int)->int:
dp =[1]* n
for i inrange(m -2,-1,-1):for j inrange(n -2,-1,-1):
dp[j]+= dp[j +1]return dp[0]
publicclassSolution{publicintuniquePaths(int m,int n){int[] dp =newint[n];Arrays.fill(dp,1);for(int i = m -2; i >=0; i--){for(int j = n -2; j >=0; j--){
dp[j]+= dp[j +1];}}return dp[0];}}
classSolution{public:intuniquePaths(int m,int n){
vector<int>dp(n,1);for(int i = m -2; i >=0; i--){for(int j = n -2; j >=0; j--){
dp[j]+= dp[j +1];}}return dp[0];}};
classSolution{/**
* @param {number} m
* @param {number} n
* @return {number}
*/uniquePaths(m, n){let dp =newArray(n).fill(1);for(let i = m -2; i >=0; i--){for(let j = n -2; j >=0; j--){
dp[j]+= dp[j +1];}}return dp[0];}}
publicclassSolution{publicintUniquePaths(int m,int n){int[] dp =newint[n];
Array.Fill(dp,1);for(int i = m -2; i >=0; i--){for(int j = n -2; j >=0; j--){
dp[j]+= dp[j +1];}}return dp[0];}}
funcuniquePaths(m int, n int)int{
dp :=make([]int, n)for i :=range dp {
dp[i]=1}for i := m -2; i >=0; i--{for j := n -2; j >=0; j--{
dp[j]+= dp[j+1]}}return dp[0]}
class Solution {fununiquePaths(m: Int, n: Int): Int {var dp =IntArray(n){1}for(i in m -2 downTo 0){for(j in n -2 downTo 0){
dp[j]+= dp[j +1]}}return dp[0]}}
classSolution:defuniquePaths(self, m:int, n:int)->int:if m ==1or n ==1:return1if m < n:
m, n = n, m
res = j =1for i inrange(m, m + n -1):
res *= i
res //= j
j +=1return res
publicclassSolution{publicintuniquePaths(int m,int n){if(m ==1|| n ==1){return1;}if(m < n){int temp = m;
m = n;
n = temp;}long res =1;int j =1;for(int i = m; i < m + n -1; i++){
res *= i;
res /= j;
j++;}return(int) res;}}
classSolution{public:intuniquePaths(int m,int n){if(m ==1|| n ==1){return1;}if(m < n){swap(m, n);}longlong res =1;int j =1;for(int i = m; i < m + n -1; i++){
res *= i;
res /= j;
j++;}return res;}};
classSolution{/**
* @param {number} m
* @param {number} n
* @return {number}
*/uniquePaths(m, n){if(m ===1|| n ===1){return1;}if(m < n){[m, n]=[n, m];}let res =1,
j =1;for(let i = m; i < m + n -1; i++){
res *= i;
res = Math.floor(res / j);
j++;}return res;}}
publicclassSolution{publicintUniquePaths(int m,int n){if(m ==1|| n ==1){return1;}if(m < n){int temp = m;
m = n;
n = temp;}long res =1;int j =1;for(int i = m; i < m + n -1; i++){
res *= i;
res /= j;
j++;}return(int) res;}}
funcuniquePaths(m int, n int)int{if m ==1|| n ==1{return1}if m < n {
tmp := m
m = n
n = tmp
}
res, j :=1,1for i := m; i < m + n -1; i++{
res *= i
res /= j
j++}return res
}
class Solution {fununiquePaths(m: Int, n: Int): Int {if(m ==1|| n ==1)return1var m = m
var n = n
if(m < n){val tmp = m
m = n
n = tmp
}var res: Long =1var j =1for(i in m until m + n -1){
res *= i
res /= j
j++}return res.toInt()}}
classSolution{funcuniquePaths(_ m:Int,_ n:Int)->Int{if m ==1|| n ==1{return1}var m = m, n = n
if m < n {swap(&m,&n)}var res =1var j =1for i in m..<(m + n -1){
res *= i
res /= j
j +=1}return res
}}
implSolution{pubfnunique_paths(m:i32, n:i32)->i32{if m ==1|| n ==1{return1;}let(mut m,mut n)=(m asi64, n asi64);if m < n {std::mem::swap(&mut m,&mut n);}letmut res:i64=1;letmut j:i64=1;for i in m..(m + n -1){
res *= i;
res /= j;
j +=1;}
res asi32}}
Time & Space Complexity
Time complexity: O(min(m,n))
Space complexity: O(1)
Where m is the number of rows and n is the number of columns.
Common Pitfalls
Confusing Rows and Columns with Moves
Misunderstanding that an m x n grid requires m - 1 down moves and n - 1 right moves (not m and n). The total moves is (m - 1) + (n - 1) = m + n - 2.
Wrong Base Case in Recursion
Returning 1 when reaching any boundary instead of only the destination cell. The base case should trigger only at (m-1, n-1), not when hitting the last row or column.
# Wrong: counts incomplete pathsif i == m -1or j == n -1:return1# Correct: only count at destinationif i == m -1and j == n -1:return1
Integer Overflow in Math Solution
When computing combinations for larger grids, intermediate multiplication can overflow. Use long types and divide as you multiply to keep values manageable.
// Risk of overflow
res =factorial(m + n -2)/(factorial(m -1)*factorial(n -1));// Safer: multiply and divide iteratively
res *= i;
res /= j;
Sign in to join the discussion