Generate 10-digit number using a phone keypad

后端 未结 12 909
遥遥无期
遥遥无期 2020-12-13 14:12

Given a phone keypad as shown below:

1 2 3
4 5 6
7 8 9
  0

How many different 10-digit numbers can be formed starting from 1? The constrain

相关标签:
12条回答
  • 2020-12-13 14:37

    Sure it can be done in polynomial time. It's an excellent exercise in dynamic programming or memoization.

    Lets assume N (the number of digits) equals 10 for the example.

    Think of it recursively like this: How many numbers can I construct using 10 digits starting from 1?

    Answer is

    [number of 9-digit numbers starting from 8] +
    [number of 9-digit numbers starting from 6].
    

    So how many "9-digit numbers starting from 8" are there? Well,

    [number of 8-digit numbers starting from 1] +
    [number of 8-digit numbers starting from 3]
    

    and so on. Base case is reached when you get the question "How many 1-digit numbers are there starting from X" (and the answer is obviously 1).

    When it comes to complexity, the key observation is that you reuse previously computed solutions. That is for instance, the answer to "how many 5-digit numbers starting from 3" there are, can be used both when answering "how many 6-digit numbers are there starting from 8" AND "how many 6-digit numbers are there starting from 4". This reuse make the complexity collapse from exponential to polynomial.

    Let's take a closer look at the complexity of a dynamic programming solution:

    Such implementation would fill in a matrix in the following way:

    num[1][i] = 1, for all 0<=i<=9   -- there are one 1-digit number starting from X.
    
    for digits = 2...N
        for from = 0...9
            num[digits][from] = num[digits-1][successor 1 of from] +
                                num[digits-1][successor 2 of from] +
                                ...
                                num[digits-1][successor K of from]
    
    return num[N][1]                 -- number of N-digit numbers starting from 1.
    

    The algorithm simply fills the matrix one cell at a time, and the matrix is of dimension 10*N, and thus runs in linear time.


    Wrote it down from the top of my head, please correct me if there are any typos.

    0 讨论(0)
  • 2020-12-13 14:37

    Recursive memoization approach:

    vector<vector<int>> lupt = { {4, 6}, {6, 8}, {9, 7}, {4, 8}, {3, 9, 0},
                                 {},     {1,7,0}, {6, 2}, {1, 3}, {2, 4} };
    
    int numPhoneNumbersUtil(int startdigit, int& phonenumberlength, int currCount, map< pair<int,int>,int>& memT)
    {
        int noOfCombs = 0;
        vector<int> enddigits;
    
        auto it = memT.find(make_pair(startdigit,currCount));
        if(it != memT.end())
        {
            noOfCombs = it->second;
            return noOfCombs;
        }
    
        if(currCount == phonenumberlength)
        {
            return 1;
        }
    
        enddigits = lupt[startdigit];
        for(auto it : enddigits)
        {
            noOfCombs += numPhoneNumbersUtil(it, phonenumberlength, currCount + 1, memT);
        }
    
        memT.insert(make_pair(make_pair(startdigit,currCount), noOfCombs));
        return memT[make_pair(startdigit,currCount)];
    
    }
    
    int numPhoneNumbers(int startdigit, int phonenumberlength)
    {
        map<pair<int,int>,int> memT;
        int currentCount = 1; //the first digit has already been added
        return  numPhoneNumbersUtil(startdigit, phonenumberlength, currentCount, memT);
    }
    
    0 讨论(0)
  • 2020-12-13 14:41

    I'm not sure if I missed something, but reading the description of the problem I came to this solution. It has O(n) time complexity and O(1) space complexity.

    I figured that number 1 is at a corner, right? In each corner you can either move to one of the sides (4 from 9 and 3, or 6 from 7 an 1) or one of the 'vertical' sides (8 from 3 and 1, or 2 from 9 and 7). So, corners add two moves: a side move and a 'vertical' move. This is true for all four corners (1,3,9,7).

    From each side, you can either move to two corners (7 and 1 from 6, 9 and 3 from 4) or you can reach the bottom key (0). That's three moves. Two corners and one bottom.

    On the bottom key (0), you can move to both sides (4 and 6). So, in each step, you check out all possible endings for the path of the previous length (that is, how many ended on a corner, a side, a 'vertical' or the 'bottom' zero key) and then generate new ending counts according to the generation rules stated before.

    • Each corner ending adds a side and a vertical.
    • Each side ending adds 2 corners and a bottom.
    • Each vertical ending adds 2 corners.
    • Each bottom ending adds 2 sides.

    If you start from the '1' key, you start with one possible corner solution, in each step you count the number of corner, side, vertical and bottom endings of the previous step and then apply the rules to generate the next count.

    In plain javascript code.

    function paths(n) {
        //Index to 0
        var corners = 1;
        var verticals = 0;
        var bottom = 0;
        var sides = 0;
    
        if (n <= 0) {
            //No moves possible for paths without length 
            return 0;
        }
    
        for (var i = 1; i < n; i++) {
            var previousCorners = corners;
            var previousVerticals = verticals;
            var previousBottom = bottom;
            var previousSides = sides;
    
            sides = 1 * previousCorners + 2 * previousBottom;
            verticals = 1 * previousCorners;
            bottom = 1 * previousSides;
            corners = 2 * previousSides + 2 * previousVerticals;
            //console.log("Moves: %d, Length: %d, Sides: %d, Verticals: %d, Bottom: %d, Corners: %d, Total: %d", i, i + 1, sides, verticals, bottom, corners, sides+verticals+bottom+corners);  
        }
    
        return sides + verticals + bottom + corners;
    
    }
    
    for (var i = 0; i <= 10; i++) {
        console.log(paths(i));  
    }
    
    0 讨论(0)
  • 2020-12-13 14:42

    This problem may be also modelled as a Constraint satisfaction problem (aka CSP for short).

    I suggest to use the Minion solver (fast and scalable) that you can find here.

    Modelling maybe tedious and time consumming (steep learning curve).

    Instead of using Minion language input, my advice is to formulate the model with solver independent modelling language such as ESSENCE and find a converter accordingly.

    0 讨论(0)
  • 2020-12-13 14:46

    A simpler answer.

    #include<stdio.h>
    
    int a[10] = {2,2,2,2,3,0,3,2,2,2};
    int b[10][3] = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
    
    int count(int curr,int n)
    {
        int sum = 0;
        if(n==10)
            return 1;
        else
        {
            int i = 0;
            int val = 0;
            for(i = 0; i < a[curr]; i++)
            {
                val = count(b[curr][i],n+1);
                sum += val;
            }
            return sum;
        }
    }
    
    int main()
    {
        int n = 1;
        int val = count(1,0);
        printf("%d\n",val);
    }
    

    celebrate!!

    0 讨论(0)
  • 2020-12-13 14:47

    Method returns list of 10 digit numbers starting with 1. Again the count is 1424.

      public ArrayList<String> getList(int digit, int length, String base ){
        ArrayList<String> list = new ArrayList<String>();
        if(length == 1){
            list.add(base);
            return list;
        }
        ArrayList<String> temp;
    
        for(int i : b[digit]){
            String newBase = base +i;
            list.addAll(getList(i, length -1, newBase ));
        }
        return list;
    }
    
    0 讨论(0)
提交回复
热议问题