jazzpeh

Number of paths to reach last cell

Jazz Peh, May 17, 2020

Given a (n,n) 2D matrix and a total cost to reach the destination cell, we need to start from (0,0) cell and go till (n-1,n-1) cell. Assuming that each cell has a cost associated and we can only go right or down from current cell. Find the number of ways to reach destination cell with given total cost.

Example
-----------
input: [
  [ 4, 7, 1, 6 ],
  [ 5, 7, 3, 9 ],
  [ 3, 2, 1, 2 ],
  [ 7, 1, 6, 3 ]
]
cost: 25
output: 2
Explanation: 4->7->1->3->1->6->3 and 4->5->7->4->1->2->3

Solution

This problem can be solved by applying Divide and Conquer to break down the problem into smaller subproblems. Thus, by solving these subproblems, we will arrive at our final answer. Let’s try to derive the subproblems.

1. f(2, 3, 22) -> e.g. 5 ways ---|---sum
2. f(3, 2, 22) -> e.g. 4 ways ---|

We shall start by looking at the last cell. In order to reach the last cell, we know that the traversal can only go right or down. Therefore, it meant that from the last cell, we can only go left or up. If the given cost is 25, we know that in order to reach the last cell which has a value of 3, the cell before need to only have a value of 22. Hence, if we can solve these, by summing them up we can then get our final answer.

Code Algortihm

function numPaths(mat: number[][], row: number, col: number, cost: number): number {
  if (cost < 0) return 0;

  remainingCost = cost - mat[row][col];

  if (row === 0 && col === 0) {
    return remainingCost === 0 ? 1: 0;
  }

  if (row === 0) {
    return numPaths(mat, 0, col - 1, remainingCost);
  }

  if (col === 0) {
    return numPaths(mat, row - 1, 0, remainingCost);
  }

  const fromUp = numPaths(mat, row - 1, col, remainingCost);
  const fromLeft = numPaths(mat, row, col - 1, remainingCost);

  return fromUp + fromLeft;
}

Optimisation

Let’s look at the an example of the recursion tree see if we need to optimise this solution.

                                   p(3,3,25)
                                  /          \
                              /                \
                          /                      \
                      p(2,3,22)                  p(3,2,22)
                    /        \                  /       \
                  /             \           p(2,2,20)   p(3,1,20)
            p(1,3,20)         p(2,2,20)     /      \
            /         \                 p(1,2,19) p(2,1,19)
          /             \
      p(0,3,11)       p(1,2,11)
      |               /        \
      |-p(0,2,5)   p(0,2,8)   p(1,1,8)
      |-p(0,3,4)   |
                   |-p(0,1,7)

We are computing a new of the subproblems more than once such as p(2,2,20). We can then apply Dynamic Programming to optimise the solution.

type HashTable = {
  [x: number]: {
    [y: number]: {
      [z: number]: number;
    };
  };
};

const dp: HashTable = {};

function numPaths(mat: number[][], row: number, col: number, cost: number): number {
  if (cost < 0) return 0;

  remainingCost = cost - mat[row][col];

  if (row === 0 && col === 0) {
    return remainingCost === 0 ? 1: 0;
  }

  if (!(row in dp)) {
    dp[row] = {};
  }

  if (!(col in dp)) {
    dp[col] = {};
  }

  if (!(cost in dp[row][col])) {
    if (row === 0) {
      dp[row][col][cost] = numPaths(mat, 0, col - 1, remainingCost);
    } else if (col === 0) {
      dp[row][col][cost] = numPaths(mat, row - 1, 0, remainingCost);
    } else {
      const fromUp = numPaths(mat, row - 1, col, remainingCost);
      const fromLeft = numPaths(mat, row, col - 1, remainingCost);
      dp[row][col][cost] = fromUp + fromLeft;
    }
  }

  return dp[row][col][cost];
}

Bottom Up Approach

We can now then apply some reverse engineer to use the Bottom Up Approach (also known as Tabulation). Firstly, let’s start from looking at the Top Down Approach using a matrix table.

C1C2C3C4
R1?R1C1R1C2R1C3
R2R1C1R1C2+R2C1R1C3+R2C2R1C4+R2C3
R3R2C1R2C2+R3C1R2C3+R3C2R2C4+R3C3
R4R3C1R3C2+R4C1R3C3+R4C2R3C4+R4C3

For our top down approach, in order to get the solution at R1C1, we entered from R4C4 and worked our way up, checking the cost as we go along. Hence, in order to perform bottom up approach. However, this is a tricky scenario because typically at our base condition, we have a value, instead, at R1C1 we are dependent on the other cells for the remaining cost. Hence, we will need to apply the same format, starting from the bottom and working our way up to R1C1 with a twist. I will cover it in the next time as it is going to be long post by itself.


Explore more like this

© 2020 Jazz Peh, Built with Gatsby