Generate 10-digit number using a phone keypad

后端 未结 12 910
遥遥无期
遥遥无期 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:47

    I implemented both brute force and dynamic programming models

    import queue
    
    
    def chess_numbers_bf(start, length):
        if length <= 0:
            return 0
        phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
        total = 0
        q = queue.Queue()
        q.put((start, 1))
    
        while not q.empty():
            front = q.get()
            val = front[0]
            len_ = front[1]
            if len_ < length:
                for elm in phone[val]:
                    q.put((elm, len_ + 1))
            else:
                total += 1
        return total
    
    
    def chess_numbers_dp(start, length):
        if length <= 0:
            return 0
    
        phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
        memory = {}
    
        def __chess_numbers_dp(s, l):
            if (s, l) in memory:
                return memory[(s, l)]
            elif l == length - 1:
                memory[(s, l)] = 1
                return 1
            else:
                total_n_ways = 0
                for number in phone[s]:
                    total_n_ways += __chess_numbers_dp(number, l+1)
                memory[(s, l)] = total_n_ways
                return total_n_ways
        return __chess_numbers_dp(start, 0)
    
    
    # bf
    for i in range(0, 10):
        print(i, chess_numbers_bf(3, i))
    print('\n')
    
    for i in range(0, 10):
        print(i, chess_numbers_bf(9, i))
    print('\n')
    
    # dp
    for i in range(0, 10):
        print(i, chess_numbers_dp(3, i))
    print('\n')
    
    # dp
    for i in range(0, 10):
        print(i, chess_numbers_dp(9, i))
    print('\n')
    
    0 讨论(0)
  • 2020-12-13 14:52

    This can be done in O(log N). Consider the keypad and the possible moves on it as a graph G(V, E) where vertices are the available digits and edges say which digits can follow which. Now for each output position i we can form a vector Paths(i) containing the number of different paths each vertex can be reached in. Now it's pretty easy to see that for a given position i and digit v, the possible paths that it can be reached through is the sum of the different paths that possible preceding digits could be reached through, or Paths(i)[v] = sum(Paths(i-1)[v2] * (1 if (v,v2) in E else 0) for v2 in V ). Now, this is taking the sum of each position the preceding vector times a corresponding position in a column of the adjacency matrix. So we can simplify this as Paths(i) = Paths(i-1) · A, where A is the adjacency matrix of the graph. Getting rid of the recursion and taking advantage of associativity of matrix multiplication, this becomes Paths(i) = Paths(1) · A^(i-1). We know Paths(1): we have only one path, to the digit 1.

    The total number of paths for an n digit number is the sum of the paths for each digit, so the final algorithm becomes: TotalPaths(n) = sum( [1,0,0,0,0,0,0,0,0,0] · A^(n-1) )

    The exponentiation can be calculated via squaring in O(log(n)) time, given constant time multiplies, otherwise O(M(n) * log(n)) where M(n) is the complexity of your favorite arbitrary precision multiplication algorithm for n digit numbers.

    0 讨论(0)
  • 2020-12-13 15:00

    Run time constant time solution:

    #include <iostream>
    
    constexpr int notValid(int x, int y) {
    return !(( 1 == x && 3 == y ) || //zero on bottom.
             ( 0 <= x && 3 > x && //1-9
               0 <= y && 3 > y ));
    }
    
    class Knight {
        template<unsigned N > constexpr int move(int x, int y) {
            return notValid(x,y)? 0 : jump<N-1>(x,y);
        }
    
        template<unsigned N> constexpr int jump( int x, int y ) {
            return  move<N>(x+1, y-2) +
                move<N>(x-1, y-2) +
                move<N>(x+1, y+2) +
                move<N>(x-1, y+2) +
                move<N>(x+2, y+1) +
                move<N>(x-2, y+1) +
                move<N>(x+2, y-1) +
                move<N>(x-2, y-1);
        }
    
    public:
        template<unsigned N> constexpr int count() {
            return move<N-1>(0,1) + move<N-1>(0,2) +
                move<N-1>(1,0) + move<N-1>(1,1) + move<N-1>(1,2) +
                move<N-1>(2,0) + move<N-1>(2,1) + move<N-1>(2,2);
        }
    };
    
    template<> constexpr int Knight::move<0>(int x, int y) { return notValid(x,y)? 0 : 1; }
    template<> constexpr int Knight::count<0>() { return 0; } //terminal cases.
    template<> constexpr int Knight::count<1>() { return 8; }
    
    
    int main(int argc, char* argv[]) {
        static_assert( ( 16 == Knight().count<2>() ), "Fail on test with 2 lenght" );  // prof of performance
        static_assert( ( 35 == Knight().count<3>() ), "Fail on test with 3 lenght" );
    
        std::cout<< "Number of valid Knight phones numbers:" << Knight().count<10>() << std::endl;
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-13 15:01

    I decided to tackle this problem and make it as extensible as I can. This solution allows you to:

    Define your own board (phone pad, chess board, etc.)

    Define your own chess piece (Knight, Rook, Bishop, etc.); you will have to write the concrete class and generate it from the factory.

    Retrieve several pieces of information through some useful utility methods.

    The classes are as follows:

    PadNumber: Class defining a button on the phone pad. Could be renamed to 'Square' to represent a board square.

    ChessPiece: Abstract class that defines fields for all chess pieces.

    Movement: Interface that defines movement methods and allows for factory generation of pieces.

    PieceFactory: Factory class to generate Chess pieces.

    Knight: Concrete class that inherits from ChessPiece and implements Movement

    PhoneChess: Entrance class.

    Driver: Driver code.

    OK, here's the code :)

    package PhoneChess;
    
    import java.awt.Point;
    
    public class PadNumber {
    
    private String number = "";
    private Point coordinates = null;
    
    public PadNumber(String number, Point coordinates)
    {
        if(number != null && number.isEmpty()==false)
            this.number = number;
        else
            throw new IllegalArgumentException("Input cannot be null or empty.");
    
        if(coordinates == null || coordinates.x < 0 || coordinates.y < 0)
            throw new IllegalArgumentException();
        else
            this.coordinates = coordinates;
    
    }
    
    public String getNumber()
    {
        return this.number;
    }
    public Integer getNumberAsNumber()
    {
        return Integer.parseInt(this.number);
    }
    
    public Point getCoordinates()
    {
        return this.coordinates;
    }
    public int getX()
    {
        return this.coordinates.x;
    }
    public int getY()
    {
        return this.coordinates.y;
    }
    
    }
    

    ChessPiece

    package PhoneChess;
    
    import java.util.HashMap;
    import java.util.List;
    
    public abstract class ChessPiece implements Movement {
    
    protected String name = "";
    protected HashMap<PadNumber, List<PadNumber>> moves = null;
    protected Integer fullNumbers = 0;
    protected int[] movesFrom = null;
    protected PadNumber[][] thePad = null;
    }
    

    Movement Interface:

    package PhoneChess;
    
    import java.util.List;
    
    public interface Movement 
    {
    public Integer findNumbers(PadNumber start, Integer digits);
    public abstract boolean canMove(PadNumber from, PadNumber to);
    public List<PadNumber> allowedMoves(PadNumber from);
    public Integer countAllowedMoves(PadNumber from);
    }
    

    PieceFactory

    package PhoneChess;
    
    public class PieceFactory 
    {
        public ChessPiece getPiece(String piece, PadNumber[][] thePad)
        {
        if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
            throw new IllegalArgumentException("Invalid pad");
        if(piece == null)
            throw new IllegalArgumentException("Invalid chess piece");
    
        if(piece.equalsIgnoreCase("Knight"))
            return new Knight("Knight", thePad);
        else
            return null;
    }
    }
    

    Knight class

    package PhoneChess;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    
    public final class Knight extends ChessPiece implements Movement {
    
    /**Knight movements
     * One horizontal, followed by two vertical
     * Or 
     * One vertical, followed by two horizontal
     * @param name
     */
    
    public Knight(String name, PadNumber[][] thePad)
    {
        if(name == null || name.isEmpty() == true)
            throw new IllegalArgumentException("Name cannot be null or empty");
    
        this.name = name;
        this.thePad = thePad;
        this.moves = new HashMap<>();
    }
    
    
    private Integer fullNumbers = null;
    
    @Override
    public Integer findNumbers(PadNumber start, Integer digits) 
    {
        if(start == null || "*".equals(start.getNumber()) || "#".equals(start.getNumber()) ) { throw new IllegalArgumentException("Invalid start point"); }
        if(start.getNumberAsNumber() == 5) { return 0; } //Consider adding an 'allowSpecialChars' condition
        if(digits == 1) { return 1; };
    
        //Init
        this.movesFrom = new int[thePad.length * thePad[0].length];
        for(int i = 0; i < this.movesFrom.length; i++)
            this.movesFrom[i] = -1;
    
        fullNumbers = 0;
        findNumbers(start, digits, 1);      
        return fullNumbers;
    }
    
    private void findNumbers(PadNumber start, Integer digits, Integer currentDigits)
    {
        //Base condition
        if(currentDigits == digits)
        {
            //Reset
            currentDigits = 1; 
            fullNumbers++; 
            return; 
        }
        if(!this.moves.containsKey(start))
            allowedMoves(start);
    
        List<PadNumber> options = this.moves.get(start);
        if(options != null)
        {
            currentDigits++; //More digits to be got
            for(PadNumber option : options)
                findNumbers(option, digits, currentDigits);
        }
    }
    
    @Override
    public boolean canMove(PadNumber from, PadNumber to) 
    {
        //Is the moves list available?
        if(!this.moves.containsKey(from.getNumber()))
        {
            //No? Process.
            allowedMoves(from);
        }
        if(this.moves.get(from) != null)
        {
            for(PadNumber option : this.moves.get(from))
            {
                if(option.getNumber().equals(to.getNumber()))
                    return true;
            }
        }
        return false;
    
    }
    
    /***
     * Overriden method that defines each Piece's movement restrictions.
     */
    @Override
    public List<PadNumber> allowedMoves(PadNumber from) 
    {
        //First encounter
        if(this.moves == null)
            this.moves = new HashMap<>();
    
    
        if(this.moves.containsKey(from))
            return this.moves.get(from);
        else
        {
            List<PadNumber> found = new ArrayList<>();
            int row = from.getY();//rows
            int col = from.getX();//columns
    
            //Cases:
            //1. One horizontal move each way followed by two vertical moves each way
            if(col-1 >= 0 && row-2 >= 0)//valid
            {
                if(thePad[row-2][col-1].getNumber().equals("*") == false && 
                        thePad[row-2][col-1].getNumber().equals("#") == false)
                {
                    found.add(thePad[row-2][col-1]);
                    this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
                }
    
            }
            if(col-1 >= 0 && row+2 < thePad.length)//valid
            {
                if(thePad[row+2][col-1].getNumber().equals("*") == false && 
                        thePad[row+2][col-1].getNumber().equals("#") == false)
                {
                    found.add(thePad[row+2][col-1]);
                    this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
                }
            }
            if(col+1 < thePad[0].length && row+2 < thePad.length)//valid
            {
                if(thePad[row+2][col+1].getNumber().equals("*") == false && 
                        thePad[row+2][col+1].getNumber().equals("#") == false)
                {
                    found.add(thePad[row+2][col+1]);
                    this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
                }
            }
            if(col+1 < thePad[0].length && row-2 >= 0)//valid
            {
                if(thePad[row-2][col+1].getNumber().equals("*") == false && 
                        thePad[row-2][col+1].getNumber().equals("#") == false)
                found.add(thePad[row-2][col+1]);
            }
            //Case 2. One vertical move each way follow by two horizontal moves each way
    
            if(col-2 >= 0 && row-1 >= 0)
            {
                if(thePad[row-1][col-2].getNumber().equals("*") == false && 
                        thePad[row-1][col-2].getNumber().equals("#") == false)
                found.add(thePad[row-1][col-2]);
            }
            if(col-2 >= 0 && row+1 < thePad.length)
            {
                if(thePad[row+1][col-2].getNumber().equals("*") == false && 
                        thePad[row+1][col-2].getNumber().equals("#") == false)
                found.add(thePad[row+1][col-2]);
            }
    
            if(col+2 < thePad[0].length && row-1 >= 0)
            {
                if(thePad[row-1][col+2].getNumber().equals("*") == false && 
                        thePad[row-1][col+2].getNumber().equals("#") == false)
                found.add(thePad[row-1][col+2]);
            }
            if(col+2 < thePad[0].length && row+1 < thePad.length)
            {
                if(thePad[row+1][col+2].getNumber().equals("*") == false && 
                        thePad[row+1][col+2].getNumber().equals("#") == false)
                found.add(thePad[row+1][col+2]);
            }
    
            if(found.size() > 0)
            {
                this.moves.put(from, found);
                this.movesFrom[from.getNumberAsNumber()] = found.size();
            }
            else
            {
                this.moves.put(from, null); //for example the Knight cannot move from 5 to anywhere
                this.movesFrom[from.getNumberAsNumber()] = 0;
            }
        }
    
        return this.moves.get(from);
    
    
    }
    
    @Override
    public Integer countAllowedMoves(PadNumber from) 
    {
        int start = from.getNumberAsNumber();
    
        if(movesFrom[start] != -1)
            return movesFrom[start];
        else
        {
            movesFrom[start] = allowedMoves(from).size();
        }
        return movesFrom[start];
    }
    
    @Override
    public String toString()
    {
        return this.name;
    }
    
    }
    

    PhoneChess entrant class

    package PhoneChess;
    
    
    public final class PhoneChess 
    {
    private ChessPiece thePiece = null;
    private PieceFactory factory = null;
    
    public ChessPiece ThePiece()
    {
        return this.thePiece;
    }
    
    public PhoneChess(PadNumber[][] thePad, String piece)
    {
        if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
            throw new IllegalArgumentException("Invalid pad");
        if(piece == null)
            throw new IllegalArgumentException("Invalid chess piece");
    
        this.factory = new PieceFactory();
        this.thePiece = this.factory.getPiece(piece, thePad);
    }
    
    public Integer findPossibleDigits(PadNumber start, Integer digits)
    {
        if(digits <= 0)
            throw new IllegalArgumentException("Digits cannot be less than or equal to zero");
    
        return thePiece.findNumbers(start, digits);
    }
    
    public boolean isValidMove(PadNumber from, PadNumber to)
    {
        return this.thePiece.canMove(from, to);
    }
    
    }
    

    Driver Code:

    public static void main(String[] args) {
    
    
        PadNumber[][] thePad = new PadNumber[4][3];
        thePad[0][0] = new PadNumber("1", new Point(0,0));
        thePad[0][1] = new PadNumber("2", new Point(1,0));
        thePad[0][2] = new PadNumber("3",new Point(2,0));
        thePad[1][0] = new PadNumber("4",new Point(0,1));
        thePad[1][1] = new PadNumber("5",new Point(1,1));
        thePad[1][2] = new PadNumber("6", new Point(2,1));
        thePad[2][0] = new PadNumber("7", new Point(0,2));
        thePad[2][1] = new PadNumber("8", new Point(1,2));
        thePad[2][2] = new PadNumber("9", new Point(2,2));
        thePad[3][0] = new PadNumber("*", new Point(0,3));
        thePad[3][1] = new PadNumber("0", new Point(1,3));
        thePad[3][2] = new PadNumber("#", new Point(2,3));
    
        PhoneChess phoneChess = new PhoneChess(thePad, "Knight");
        System.out.println(phoneChess.findPossibleDigits(thePad[0][1],4));
    }
    
    }
    
    0 讨论(0)
  • 2020-12-13 15:01

    Recursive function in Java:

    public static int countPhoneNumbers (int n, int r, int c) {
            if (outOfBounds(r,c)) {
                return 0;
            } else {
                char button = buttons[r][c];
                if (button  == '.') {
                    // visited
                    return 0;
                }  else {
                    buttons[r][c] = '.'; // record this position so don't revisit.
                    // Count all possible phone numbers with one less digit starting
                    int result=0;
                    result = countPhoneNumbers(n-1,r-2,c-1)
                                             + countPhoneNumbers(n-1,r-2,c+1)
                                             + countPhoneNumbers(n-1,r+2,c-1)
                                             + countPhoneNumbers(n-1,r+2,c+1)
                                             + countPhoneNumbers(n-1,r-1,c-2)
                                             + countPhoneNumbers(n-1,r-1,c+2)
                                             + countPhoneNumbers(n-1,r+1,c-2)
                                             + countPhoneNumbers(n-1,r+1,c+2);
                    }
                    buttons[r][c] = button; // Remove record from position.
                    return result; 
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-13 15:02
    //Both the iterative and recursive with memorize shows count as 1424 for 10 digit numbers starting with 1. 
    int[][] b = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
    public int countIterative(int digit, int length) {
        int[][] matrix = new int[length][10];
        for(int dig =0; dig <=9; dig++){
              matrix[0][dig] = 1;
        }
        for(int len = 1; len < length; len++){
            for(int dig =0; dig <=9; dig++){
              int sum = 0;
              for(int i : b[dig]){
                sum += matrix[len-1][i];
              }
              matrix[len][dig] = sum;
            }
        }
        return matrix[length-1][digit];
    }
    
    public int count(int index, int length, int[][] matrix ){
        int sum = 0;
        if(matrix[length-1][index] > 0){
            System.out.println("getting value from memoize:"+index + "length:"+ length);
            return matrix[length-1][index];
        }
        if( length == 1){
            return 1;
        }
        for(int i: b[index] )  {
             sum += count(i, length-1,matrix);
        }
        matrix[length-1][index] = sum;
        return sum;
    }
    
    0 讨论(0)
提交回复
热议问题