触摸屏调出虚拟键盘_手机数字键盘问题

本课程是从少年编程网转载的课程,目标是向中学生详细介绍计算机比赛涉及的编程语言,数据结构和算法。编程学习最好使用计算机,请登陆 www.3dian14.org (免费注册,免费学习)。

我们知道,通过手机上的键盘按键(不管是之前手机上的物理键盘还是现在触摸屏上的虚拟键盘),可以按出相应的电话号码。

44d460875a34beb8ba2fe85157bccded.png

我们来看一个有趣的问题:

假设您只能按键盘上的数字键(0-9),而不允许按底行的角按钮(即*和#)。这样,您可以按出只有数字0-9组成的数字串。

再假设您按了某个数字键之后,接着只能继续按当前数字按键或者位于当前数字键的上,左,右或下的数字键。也就是下一个按键是有限制的,不是任意选择的。

例如,你按了数字5以后,下次只能按数字5,2,4,6,或8。按了数字0以后,下次只能再按数字0或8。

好,现在来看今天的问题,在上述限制条件下,请找出您能按出的长度为N的数字串的数量。

我们来看些例子:

当N = 1,您可以按0,1,2,...,9,也就是可能的数字串数量为10个。

对于N = 2,可能的数字是00,08 11,12,14 22,21,23,25等等。我们具体看一下:

  • 如果我们以0开头,则有效数字串将为00、08(计数:2)

  • 如果我们从1开始,有效数字串将是11、12、14(计数:3)

  • 如果我们从2开始,有效数字串将是22、21、23.25(计数:4)

  • 如果我们以3开头,则有效数字串将为33、32、36(计数:3)

  • 如果我们从4开始,有效数字串将是44,41,45,47(计数:4)

  • 如果我们从5开始,有效数字串将是55、54、52、56、58(计数:5)

  • 如果我们从6开始,有效数字串将是66,63、65、69(计数:4)

  • 如果我们从7开始,有效数字串将是77,74、78(计数:3)

  • 如果我们从8开始,有效数字串将是88,85、87、89、80(计数:5)

  • 如果我们从9开始,有效数字串将是99,96、98(计数:3)

因此总数为2+3+4+3+4+5+4+3+5+3=36。

2ba69fff0a11a1e6ee0da679ed2c1e16.png

算法分析

在上述限制条件下,对于给定的N,我们需要统计所有长度为N的数字串的个数。

1)N = 1是最简单的情况,可能的数量为10(0,1,2,3,…,9)

2)对于N>1,我们需要从某个按键开始,然后重复选择按键或移至四个方向(向上,向左,向右或向下)中的任意一个,然后转到一个有效按键(不应转到*,#)。继续执行此操作,直到获得N个长度数字(深度优先遍历)。

递归是很顺理成章的解决方案。

我们把键盘看成4X3的矩形网格(4行3列)

假设Count(i,j,N)代表从位置(i,j)开始的长度为N的数字串的个数

1)如果N = 1

     Count(i,j,N)= 10

2)其他情况:

     Count(i,j,N)= 所有Count(r,c,N-1)的总和,其中(r,c)是       新的从当前位置有效移动长度1之后的新位置(i,j)

ff3f747a0711dca7da3ad714e25a8c61.png

朴素递归算法

以下是上述递归公式的实现例子。

//朴素递归算法

#include  

//从当前位置移动到左、上、右、下的位置

int row[] = {0, 0, -1, 0, 1}; 

int col[] = {0, -1, 0, 1, 0}; 

// 返回从位置(i,j)开始的长度为N的数字串的个数

int getCountUtil(char keypad[][3], int i, int j, int n) 

    if (keypad == NULL || n <= 0) 

        return 0; 

   //对于当前键,长度为1只有一种可能

    if (n == 1) 

        return 1; 

    int k=0, move=0, ro=0, co=0, totalCount = 0; 

  //从当前位置向左,向上,向右,向下移动

  //如果新位置有效,从该新位置开始,计算长度为(n-1)的数字串个数,并加上到目前为止获得的计数

  for (move=0; move<5; move++) 

    { 

        ro = i + row[move]; 

        co = j + col[move]; 

        if (ro >= 0 && ro <= 3 && co >=0 && co <= 2 && 

           keypad[ro][co] != '*' && keypad[ro][co] != '#') 

        { 

            totalCount += getCountUtil(keypad, ro, co, n-1); 

        } 

    } 

    return totalCount; 

//返回长度为n的所有可能数字串的计数

int getCount(char keypad[][3], int n) 

    // 递归基础

    if (keypad == NULL || n <= 0) 

        return 0; 

    if (n == 1) 

        return 10; 

    int i=0, j=0, totalCount = 0; 

    for (i=0; i<4; i++)  //行循环

    { 

        for (j=0; j<3; j++)   // 列循环

        { 

            // Process for 0 to 9 digits 

            if (keypad[i][j] != '*' && keypad[i][j] != '#') 

            { 

                // Get count when number is starting from key 

                // position (i, j) and add in count obtained so far 

                //计算从位置(i,j)的键开始的数字串个数,并添加到目前为止获得的计数

                totalCount += getCountUtil(keypad, i, j, n); 

            } 

        } 

    } 

    return totalCount; 

// 主程序

int main(int argc, char *argv[]) 

   char keypad[4][3] = {{'1','2','3'}, 

                        {'4','5','6'}, 

                        {'7','8','9'}, 

                        {'*','0','#'}}; 

   printf("Count for numbers of length %d: %dn", 1, getCount(keypad, 1)); 

   printf("Count for numbers of length %d: %dn", 2, getCount(keypad, 2)); 

   printf("Count for numbers of length %d: %dn", 3, getCount(keypad, 3)); 

   printf("Count for numbers of length %d: %dn", 4, getCount(keypad, 4)); 

   printf("Count for numbers of length %d: %dn", 5, getCount(keypad, 5)); 

   return 0; 

c37e9182b2cb8c8d56c0f8b95e0b1a9b.png

动态规划优化

我们可以看到,在上述递归算法中,较长的遍历路径上有很多是对较短的遍历路径的重复计算。例如:

例如,请参见以下两个图。

下图列出从数字8开始的长度为4的部分数字串。

91958429421fa8290aa453937b716268.png

而下图列出从数字5开始的长度为4的部分数字串。

df9c258383484367525e1a0b7d5a9d34.png

从图中可以看到有些重复的遍历(例如4-> 1,6-> 3,8-> 9、8-> 7等等)。该问题明显具有以下两个属性:最佳子结构和重叠子问题,因此可以使用动态编程有效地解决。

下面是用动态规划算法优化后的一种实现例子。请大家运行这个程序,并体会是如何保存中间结果的。

//动态规划算法

#include  

// 返回从位置(i,j)开始的长度为N的数字串的个数

int getCount(char keypad[][3], int n) 

if(keypad == NULL || n <= 0) 

return 0; 

if(n == 1) 

return 10; 

//从当前位置移动到左、上、右、下的位置

int row[] = {0, 0, -1, 0, 1}; 

int col[] = {0, -1, 0, 1, 0}; 

//为简单起见,取count [i] [j]将存储

//以数字i开头的长度为j的数字串个数

int count[10][n+1]; 

int i=0, j=0, k=0, move=0, ro=0, co=0, num = 0; 

int nextNum=0, totalCount = 0; 

//以数字i开头且长度为0和1的数字

for (i=0; i<=9; i++) 

count[i][0] = 0; 

count[i][1] = 1; 

//自下而上,计算长度为2、3、4,...,n的数字串个数

for (k=2; k<=n; k++) 

for (i=0; i<4; i++) // 键盘行循环

for (j=0; j<3; j++) // 键盘列循环

// 处理数字0-9

if (keypad[i][j] != '*' && keypad[i][j] != '#') 

//统计数字keypad[i][j]开头的并且长度为k的数字串个数,数字keypad[i][j]

//是第一个数字,我们需要再找(k-1)个数字

num = keypad[i][j] - '0'; 

count[num][k] = 0; 

  //从当前位置向左,向上,向右,向下移动

  //如果新位置有效,从该新位置开始,计算长度为(n-1)的数字串个数,并加上到目前为止获得的计数

for (move=0; move<5; move++) 

ro = i + row[move]; 

co = j + col[move]; 

if (ro >= 0 && ro <= 3 && co >=0 && co <= 2 && 

keypad[ro][co] != '*' && keypad[ro][co] != '#') 

nextNum = keypad[ro][co] - '0'; 

count[num][k] += count[nextNum][k-1]; 

//累加计算所有长度为 n从数字0,1,2,...,9起始的数字串个数

totalCount = 0; 

for (i=0; i<=9; i++) 

totalCount += count[i][n]; 

return totalCount; 

// 主程序

int main(int argc, char *argv[]) 

char keypad[4][3] = {{'1','2','3'}, 

{'4','5','6'}, 

{'7','8','9'}, 

{'*','0','#'}}; 

printf("Count for numbers of length %d: %dn", 1, getCount(keypad, 1)); 

printf("Count for numbers of length %d: %dn", 2, getCount(keypad, 2)); 

printf("Count for numbers of length %d: %dn", 3, getCount(keypad, 3)); 

printf("Count for numbers of length %d: %dn", 4, getCount(keypad, 4)); 

printf("Count for numbers of length %d: %dn", 5, getCount(keypad, 5)); 

return 0; 

60d6ba18fcd3bfc77e6c149181867480.png

思考题

上述动态编程方法也需要O(n)时间运行,因为只有一个for循环运行n次,其他for循环需运行恒定时间。

但是上述算法需要O(n)辅助空间存放临时数据。事实上可以看到,第n次迭代仅需要第(n-1)次迭代中的数据,因此我们不需要保留较旧迭代中的数据,可以使用只有两个大小为10的数组的动态编程方法来实现,请你试试看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值