I have m x n grid. m >= 1 ; n >= 1
I have item in the top-left corner and need to reach bottom-right corner of the grid.
Item can only move either down or right.
The problem with your approach is you're taking the same steps repeatedly. It's the first brute-force approach that someone should try.
For starter, you can try to increase the recursion limit for python.
import sys
sys.setrecursionlimit(1500)
But it will fail if you start increasing m
, or n
. As the complexity grows exponentially.
One way to improve is to break the problem down into smaller parts and solve for the smaller parts and merge them into the final solution.
Think, you are in the green position and want to go to the blue one. That's the main solution. But, let's imagine the smaller sub-grid with red boundary, the red grid has a starting point at the orange marker and endpoint at blue, now let's say in some magical way we know the solution for the red sub-grid, can't we just merge the solution for going from green to orange + red grid part?
Now, this recursive idea can be implemented in the following way.
def numberOfPaths(m, n):
if(m == 1 or n == 1):
return 1
return numberOfPaths(m-1, n) + numberOfPaths(m, n-1) # traversal in the two possible directions
m = 20
n = 20
print(numberOfPaths(m, n))
But the complexity is still exponential, as the program tries all possible combinations for finding a solution over and over again. What if we use a map to save all the partial solutions? We can save the solution for red sub-grid and just use it from our map without re-traversing it again?
This concept is called dynamic programming and it's very well known. So, I won't go to any details.
We can create a 2-d array answers[m][n]
it will be initialized with -1
; if we know the solution from a sub-grid m_1, n_1
we just return the answer instead of traversing.
This brings down the complexity to O(mxn)
.
import numpy as np
global answers
def numberOfPaths(m, n):
if(m == 1 or n == 1):
return 1
global answers
if answers[m][n] != -1:
return answers[m][n]
answers[m][n] = numberOfPaths(m-1, n) + numberOfPaths(m, n-1) # traversal
return answers[m][n]
m = 6
n = 6
answers = np.ones((m+1,n+1))*-1
print(numberOfPaths(m, n))
It's already a major improvement.
We can completely re-invent the problem as a combinatorial one too.
Look, there are m
rows, n
columns, if you start from top-left, you can take any set of moves (right or down), but your initial cell and final cell are fixed. So, how many possible options do you have to make moves? (m+n-2)
(initial and final cell fixed so -2) Now, from all these possible moves you can only select n-1
if we consider the columns, or m-1
if we consider the rows. So, the solution will be (m+n-2)C(n-1)
or (m+n-2)C(m-1)
.
Now, for smaller integers where m!
or n!
don't overflow (luckily python integers can handle large values easily), this can be done in linear time O(max(m,n))
. As nCr
can be calculated in terms of factorials only.