Knight Dialer - A Classic Dynamic Programming Problem

This was a very interesting problem, and a classic application of Dynamic Programming. Here is the problem: https://leetcode.com/problems/knight-dialer/

A chess knight can move as indicated in the chess diagram below:
This time, we place our chess knight on any numbered key of a phone pad (indicated above), and the knight makes N-1 hops.  Each hop must be from one key to another numbered key.
Each time it lands on a key (including the initial placement of the knight), it presses the number of that key, pressing N digits total.
How many distinct numbers can you dial in this manner?
Since the answer may be large, output the answer modulo 10^9 + 7.

Example 1:
Input: 1
Output: 10
Example 2:
Input: 2
Output: 20
Example 3:
Input: 3
Output: 46

Note:
  • 1 <= N <= 5000

The 10^9 + 7 to me is always the strongest indication that the solution should be done via DP. The also indication is that the N is very large (5000), hence if we try all the possibilities, we’re possibly talking about 9^5000, which is not tractable.

The way to think about the solution for this problem is the following: suppose that you have the solution for N-1. You know for each cell from 0..9 the solution for N-1 hops. In this case how can you get the solution for N hops? Well, what you can do is that for each cell, say cell(0), how many possibilities can you have? The number of possibilities for cell(0) is whatever the N-1 solution is for cell(4) plus whatever the N-1 solution is for cell(6). Once you do that for all cells, then you add them all up and you have the solution for N.

With that in mind, we build the solution by construction. The very first thing to do is to create a map mapping each cell to the corresponding cells based on how the knight moves (as I said above, for example, 0 maps to 4 and 6). That’s where you use the information about how the knight moves. Then you construct the solution for N=1 (trivial, no hops). From that point one, build the solution for 2…N. I use a hash table just because I find it super easy to manipulate the data structure.

Looking at the space complexity, it is a constant space. Time would be linear: N (max: 5000) * 10 (fixed) * 3 (max number of mapped cells).

Code is down below – cheers!!! ACC.


public class Solution
{
    public int KnightDialer(int N)
    {
        Hashtable map = new Hashtable();

        map.Add(0, new int[] { 4, 6, });
        map.Add(1, new int[] { 6, 8 });
        map.Add(2, new int[] { 7, 9 });
        map.Add(3, new int[] { 4, 8 });
        map.Add(4, new int[] { 0, 3, 9 });
        map.Add(5, new int[] { });
        map.Add(6, new int[] { 0, 1, 7 });
        map.Add(7, new int[] { 2, 6 });
        map.Add(8, new int[] { 1, 3 });
        map.Add(9, new int[] { 2, 4 });

        //DP
        Hashtable previous = new Hashtable();
        Hashtable current = new Hashtable();

        //N=1
        for (int i = 0; i < 10; i++)
        {
            previous.Add(i, 1);
        }
        current = (Hashtable)previous.Clone();

        //N>1
        for (int i = 1; i < N; i++)
        {
            current = new Hashtable();
            for (int j = 0; j < 10; j++)
            {
                current.Add(j, 0);
                int[] mapArray = (int[])map[j];
                for (int z = 0; z < mapArray.Length; z++)
                {
                    current[j] = ((int)current[j] + (int)previous[mapArray[z]]) % (1000000000 + 7);
                }
            }

            previous = (Hashtable)current.Clone();
        }

        int sum = 0;
        for (int i = 0; i < 10; i++)
        {
            sum = (sum + (int)current[i]) % (1000000000 + 7);
        }

        return sum;
    }
}

Comments

  1. Nice article admin thanks for share your atricle keep share your knowledge i am waiting for your new post check mens winter jackets polo shirts kindly review and reply me

    ReplyDelete
  2. love this problem! Interestingly an O(1) space solution is even easier to understand:

    MOD = 10**9 + 7

    class Solution:
    def knightDialer(self, N: int) -> int:
    d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 1
    for _ in range(N-1):
    d0, d1, d2, d3, d4, d5, d6, d7, d8, d9 = \
    (d4 + d6) % MOD, \
    (d6 + d8) % MOD, \
    (d7 + d9) % MOD, \
    (d4 + d8) % MOD, \
    (d0 + d3 + d9) % MOD, \
    0, \
    (d0 + d1 + d7) % MOD, \
    (d2 + d6) % MOD, \
    (d1 + d3) % MOD, \
    (d2 + d4) % MOD

    return (d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9) % MOD

    And similar to fibonacci sequence optimization to achieve O(N*log(N)) time complexity these additions can be first replaced with matrix multiplication and fast exponentiation algorithm, but I'm too lazy for that :) This solution is already fast enough.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Cool solution. Moving away from the hashtable (required boxing / unboxing) to a dictionary and moving to arrays instead of hashtable for the current and previous trackers reduces the runtime by 5x.

    int mod=(int)1e9+7;
    public int KnightDialer(int N) {
    Dictionary map = new Dictionary();

    map.Add(0, new int[] { 4, 6, });
    map.Add(1, new int[] { 6, 8 });
    map.Add(2, new int[] { 7, 9 });
    map.Add(3, new int[] { 4, 8 });
    map.Add(4, new int[] { 0, 3, 9 });
    map.Add(5, new int[] { });
    map.Add(6, new int[] { 0, 1, 7 });
    map.Add(7, new int[] { 2, 6 });
    map.Add(8, new int[] { 1, 3 });
    map.Add(9, new int[] { 2, 4 });

    //DP
    int[] previous = new int[10];
    int[] current = new int[10];

    //N=1
    for (int i = 0; i < 10; i++)
    {
    previous[i] = 1;
    }
    current = (int[])previous.Clone();

    //N>1
    for (int i = 1; i < N; i++)
    {
    current = new int[10];
    for (int j = 0; j < 10; j++)
    {
    current[j]= 0;
    int[] mapArray = map[j];
    for (int z = 0; z < mapArray.Length; z++)
    {
    current[j] = (current[j] + previous[mapArray[z]]) % mod;
    }
    }

    previous = (int[])current.Clone();
    }

    int sum = 0;
    for (int i = 0; i < 10; i++)
    {
    sum = (sum + current[i]) % mod;
    }

    return sum;
    }

    ReplyDelete

Post a Comment

Popular posts from this blog

Advent of Code - Day 6, 2024: BFS and FSM

Golang vs. C#: performance characteristics (simple case study)

Advent of Code - Day 7, 2024: Backtracking and Eval