Problem Statement in English

You’re given a $2$ integers m and n. They represent the number of rows and columns in a grid.

Your task is to calculate the number of paths from the origin $(0,0)$ to $(m - 1,n - 1)$.


Approach

They’ve told us that you can either move down or right at any point. So, the number of paths from a co-ordinate $(x,y)$ to $(m-1, n-1)$ (let’s call the the function that does the calculation sol) is:

$$\text{sol(x,y)} = \text{sol}(x + 1, y) + \text{sol}(x, y + 1)$$

You can actually solve this problem with just the formula. It clearly involves recursion, as can be seen from the way the function is invoked.

However, there’s one major flaw to this approach. Redundant computation.

I’m going to draw a graph, because that should make a whole lot more sense than me trying to put it into words.

    graph TD
    A((0,0)) --> B((1,0))
    A --> C((0,1))

    B --> D((2,0))
    B --> E((1,1))

    C --> F((1,1))
    C --> G((0,2))

Do you see how $(1,1)$ is called twice?

As the recursion tree generates there will be far more duplicated calls. The way we fix this, is through a technique called memoization.

All it does, is cache results, and so before we attempt to calculate the result for a co-ordinate, we check if it’s been calculated in the past. If it has, we use that stored value instead of redoing the calculation.

It’s basically a lookup table. With a fancy name.


Solution in Python


class Solution:
    def uniquePaths(self, m: int, n: int) -> int:

        # for memoization
        lookup = {}

        def recursiveCall(x: int, y: int):
            # base case
            if x == m - 1 and y == n - 1:
                return 1
            # out of bounds
            if x >= m or y >= n:
                return 0
            # if already calculated
            if (x, y) in lookup:
                return lookup[(x, y)]

            # down and right
            down = recursiveCall(x + 1, y)
            right = recursiveCall(x, y + 1)

            # store the result
            lookup[(x + 1, y)] = down
            lookup[(x, y + 1)] = right
            lookup[(x, y)] = down + right

            # return the result
            return lookup[(x, y)]

        # begin recursion
        return recursiveCall(0, 0)

Complexity

  • Time: $O(m \times n)$
    Since we iterate over $m \times n$ co-ordinates at most.

  • Space: $O(n^2)$
    Since we might cache upto $m \times n$ values in the lookup table.


And we are done.