从移动键盘计算可能的单词 - 动态编程方法

时间:2015-09-23 11:52:19

标签: algorithm data-structures dynamic-programming

鉴于移动数字键盘。您只能按向上,向左,向右或向下按钮到当前按钮。您不能按下底行角按钮(即*和#)。给定N找出给定长度的可能数量。有人要求写代码。

实施例: 对于N = 2 可能的数字:00,08 11,12,14 22,21,23,25等。我们必须打印这些数字的数量。

我尝试使用以下DP方法解决问题。但它为每个长度值给出相同的结果。

public class MobileKeypad {

    private static int findTotalNumbers(int L)
    {
        int R = 4, C = 3, count = 0;

        boolean[][][] dp = new boolean[L+1][R][C];

        int[] dx = {0, 0, -1, 0, 1};
        int[] dy = {0, -1, 0, 1, 0};

        for(int k = 0; k < L; k++)
        {
            for(int i = 0; i < R; i++)
            {
                for(int j = 0; j < C; j++)
                {
                    if(k == 0)
                    {
                        dp[k][i][j] = true;
                        continue;
                    }

                    if(i == R-1 && (j == 0 || j == C-1))
                            dp[k][i][j] = false;

                    for(int p = 0; p < dx.length; p++)
                    {
                        int x = i + dx[p];
                        int y = j + dy[p];

                        if(x >= 0 && x < R && y >= 0 && y < C && ( x!= R-1 && (y != 0 || y != C-1)))
                        {
                            if(dp[k-1][x][y] == true)
                            {
                                if(k == L-1)
                                    count++;

                                dp[k][i][j] = true;
                            }

                        }
                    }
                }
            }
        }

        return count;
    }

    public static void main(String[] args)
    {
        //Visualize a (4 x 3) matrix of mobile keypad.
        int length = 2; //It is giving the same result for every length.

        int totalNumbers = findTotalNumbers(length);

        System.out.println("Total number possible with length " + length + " is " + totalNumbers);
    }
}

错误在哪里?

编辑:我实现了答案中给出的想法如下,似乎工作正常:

public class NumberOfWays {

    //4-neighbors of the present point.
    private int[] dx = {0, -1, 0, 1};
    private int[] dy = {-1, 0, 1, 0};

    private int findWays(int[][] M, int n)
    {
        if(M == null || M.length == 0 || n <= 0)
            throw new IllegalArgumentException("The arguments are invalid.");

        int[] res = new int[n + 1];

        find(M, n, res);

        return res[n];
    }

    private void find(int[][] M, int n, int[] res)
    {
        int R = M.length;
        int C = M[0].length;

        int[][][] DP = new int[R][C][n + 1];

        for(int i = 0; i < R; i++)
        {
            for(int j = 0; j < C; j++)
            {
                if(M[i][j] != -1)
                {
                    DP[i][j][1] = 1;

                    res[1] += DP[i][j][1];
                }
            }
        }


        for(int i = 0; i < R; i++)
        {
            for(int j = 0; j < C; j++)
            {
                if(i == R-1 && (j == 0 || j == C-1))
                    continue;

                for(int k = 2; k <= n; k++)
                {
                    for(int p = 0; p < dx.length; p++) //Iterate for all neighbors of the current point.
                    {
                        int x = i + dx[p];
                        int y = j + dy[p];

                        if(isValidX(x, R) && isValidY(y, C) && M[x][y] != -1) //If M[x][y] == -1 that an invalid number i.e; bottom row corners.
                        {
                            DP[i][j][k] += DP[x][y][k-1];
                        }
                    }

                    DP[i][j][k] += DP[i][j][k-1]; //Same number can repeat also.

                    res[k] += DP[i][j][k];
                }
            }
        }

        print(DP, R, C, n);
    }

    private void print(int[][][] DP, int R, int C, int N)
    {
        for(int i = 0; i < R; i++)
        {
            for(int j = 0; j < C; j++)
            {               
                System.out.print("[" + i + ", " + j + "]: ");

                for(int k = 0; k <= N; k++)
                {
                    System.out.print("k = " + k + ": " + DP[i][j][k] + "   ");
                }

                System.out.println();
            }
        }
    }

    private boolean isValidX(int x, int R)
    {
        if(x >= 0 && x < R)
            return true;

        return false;
    }

    private boolean isValidY(int y, int C)
    {
        if(y >= 0 && y < C)
            return true;

        return false;
    }

    public static void main(String[] args)
    {
        NumberOfWays nw = new NumberOfWays();

        int[][] M = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {-1, 0, -1}};
        int n = 2;

        int noOfWays = nw.findWays(M, n);

        System.out.println("Total numbers possible with " + n + " steps: " + noOfWays);     
    }
}

2 个答案:

答案 0 :(得分:1)

在您的情况下,矩阵很小,但未指定L。如果它受到经典电话号码长度的限制,那么简单的DP就可以工作,但如果没有,你需要更快的东西。

简单的DP

让:

dp[i, j, k] = how many numbers we can form ending at [i, j] and having length k

我们有:

dp[<all>, <all>, {0, 1}] = 1 

然后,对于所有1 < k <= L

dp[i, j, k] = sum {dp[<valid neighbors of [i, j]>, k - 1]}
              +
              dp[i, j, k - 1] <- we can use the same cell twice

大小为n的方形矩阵的时间和内存复杂度:O(n^2 * L)

更复杂的DP

如果L可能非常大,则第一种方法无效。相反,我们可以使用以下DP:

dp[i, j, p, q, k] = how many numbers we can form
                    that start at [i, j],
                    end at [p, q]
                    and have length 2^k

然后重现关系是:

dp[i, j, p, q, k] = sum{dp[i, j, x, y, k - 1] * dp[x, y, p, q, k - 1]}

对于矩阵中的所有有效x, y对。

要查找给定L的实际答案,请将L写为2的幂的总和,因为这是我们在dp数组中使用的内容:

L = 2^a1 + 2^a2 + ... + 2^ak

然后,x, yp, q长度为L的路径数为:

dp[x, y, x', y', a1] * dp[x', y', x'', y'', a2] * ... * dp[x^(k-1), y^(k-1), p, q, ak]

时间复杂度为O(n^6 * log L)。在您的情况下,n = 3,您应该开始看到大约L = 200的性能改进。

答案 1 :(得分:0)

为什么这么复杂?实际上,您甚至不需要走每条可能的路径来计算可能的结果数量。

通过使用几个简单的规则,可以解析分析56 * N值,这比路径数量的指数增长要好得多:

  • 我们知道每个键可能的下一个键的数量
  • 每个密钥可以有几个前辈。通过简单地跟踪前辈的数量,我们可以轻松避免复杂的递归。

在Java中,这可能是这样的:

int possiblePaths(int n){
    //a list of all neighbours a key has
    int[][] neighbours = new int[][]{
       {0 , 7 , 8 , 9},
       {1 , 2 , 4 , 5},
       {2 , 1 , 3 , 4 , 5 , 6},
       ...
    };

    int[] pathCount = new int[10];
    int[] nextStep = new int[10];
    //at the beginning on each path ends exactly one path
    //ex.: after step1 the path (3) ends at key 3
    Arrays.fill(pathCount , 1);

    for(int step = 1 ; step < n ; step++)
    {
        for(int key = 0 ; key < 10 ; key++)
        {
            //calculate the number of paths that end at key key after step steps
            nextStep[key] = 0;
            for(int neighbour : neighbours[key])
                pathCount[key] += pathCount[neighbour];
        }

        //swap the array keeping track of the number of paths with the one generated in this step
        int[] swap = nextStep;
        pathCount = nextStep;
        nextStep = swap;
   }

   return Arrays.stream(pathCount).sum();
}

基本理念如下:

Keys-> 0   1   2   3   4    ...
Step
   1   1   1   1   1   1    ...
   2   4   4   6   4   6    ...
   ...

此表表示在给定步数后在特定键处结束的路径数。使用这样的表(我们可以很容易地生成),在x步之后以特定键结束的路径的数量可以很容易地从在该键处结束的路径的数量中扣除,并且在步骤{{1 }}。例如:
如果在步骤x - 1 4个路径以密钥x结束,并且该密钥只有一个邻居,其中5个路径结束,则总共有4 + 5个路径以y结束步骤y