jazzpeh

Number Factor Problem

Jazz Peh, May 14, 2020

Given N, count the number of ways to express N as sum of 1,3 and 4.

Example 1
-----------
n: 4
num of ways: 4
Explanation: There are four ways to express 'n': {4}, {1,3}, {3,1}, {1,1,1,1}
Example 2
-----------
n: 5
num of ways: 6
Explanation: There are six ways to express 'n': {4,1}, {1,4}, {3,1,1}, {1,3,1}, {1,1,3}, {1,1,1,1,1}

Solution

This problem can be solved by applying Divide and Conquer algorithm techniques. Firstly, let’s try to break the problem into smaller subproblems. Since we are given 3 numbers, {1,3,4}, let’s try to find the complements of those numbers and also the number of ways to form those complements. We will use the example where N = 5:

n = 5

1 | + 4 = {1,1,1,1}, {1,3}, {3,1}, {4} = 4 ways
3 | + 2 = {1,1} = 1 way
4 | + 1 = {1} = 1 way

num of ways: 4 + 1 + 1 = 6

By solving the subproblems, we arrive at the final result by getting the sum of all the answers to the subproblems.

Code Algorithm

function numWays(n: number): number {
  if (n === 0 || n === 1 || n === 2) return 1;
  if (n === 3) return 2;

  const comp1 = numWays(n - 1);
  const comp2 = numWays(n - 3);
  const comp3 = numWays(n - 4);

  return comp1 + comp2 + comp3;
}

Optimisation

Let’s look at the an example of the recursion tree using N = 8 for a larger recursive tree and see if we can optimise this solution.

                  numWays(8)
      /               |                   \
numWays(7)        numWays(5)        numWays(4)
  |                   |                   |
  |-numWays(6)       |-numWays(4)       |-numWays(3)
  |-numWays(4)       |-numWays(2)       |-numWays(1)
  |-numWays(3)       |-numWays(1)       |-numWays(0)

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

Top Down Approach

We will start off by applying Top Down Approach (also known as Memoization).

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

const dp: HashTable = {};

function numWays(n: number): number {
  if (n === 0 || n === 1 || n === 2) return 1;
  if (n === 3) return 2;

  if (!(n in dp)) {
    const comp1 = numWays(n - 1);
    const comp2 = numWays(n - 3);
    const comp3 = numWays(n - 4);

    dp[n] = comp1 + comp2 + comp3;
  }

  return dp[n];
}

Bottom Up Approach

We can now then apply some reverse engineer to use the Bottom Up Approach (also known as Tabulation). Let’s visualise how dp is filled using Top Down Approach.

IterationNum of ways
DP0DP1DP2DP3DP4DP5DP6DP7DP8
1.1112?????
2.1112????DP7+DP5+DP4
3.1112???DP6+DP4+DP3DP7+DP5+DP4
4.1112??DP5+DP3+DP2DP6+DP4+DP3DP7+DP5+DP4
5.1112?DP4+DP2+DP1DP5+DP3+DP2DP6+DP4+DP3DP7+DP5+DP4
6.1112DP3+DP1+DP0DP4+DP2+DP1DP5+DP3+DP2DP6+DP4+DP3DP7+DP5+DP4
7.11122+1+1=4DP4+DP2+DP1DP5+DP3+DP2DP6+DP4+DP3DP7+DP5+DP4
8.11122+1+1=44+1+1=6DP5+DP3+DP2DP6+DP4+DP3DP7+DP5+DP4
9.11122+1+1=44+1+1=66+2+1=9DP6+DP4+DP3DP7+DP5+DP4
10.11122+1+1=44+1+1=66+2+1=99+4+2=15DP7+DP5+DP4
11.11122+1+1=44+1+1=66+2+1=99+4+2=1515+6+4=25

We can then now reverse the process. Let’s visualize how Bottom Up Approach works here.

IterationNum of ways
DP0DP1DP2DP3DP4DP5DP6DP7DP8
1.1112?????
2.1112DP3+DP1+DP0????
3.11122+1+1=4DP4+DP2+DP1???
4.11122+1+1=44+1+1=6DP5+DP3+DP2??
5.11122+1+1=44+1+1=66+2+1=9DP6+DP4+DP3?
6.11122+1+1=44+1+1=66+2+1=99+4+2=15DP7+DP5+4
7.11122+1+1=44+1+1=66+2+1=99+4+2=1515+6+4=25
type HashTable = {
  [x: number]: number;
};

function numWays(n: number): number {
  const dp: HashTable = {
    0: 1,
    1: 1,
    2: 1,
    3: 2,
  };

  for (let i = 4; i < n + 1; ++i) {
    dp[i] = dp[i - 1] + dp[i - 3] + dp[i - 4];
  }

  return dp[n];
}

Explore more like this

© 2020 Jazz Peh, Built with Gatsby