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)}}
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:
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
}}
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;