编程之美 3.2 :电话号码对应的英文单词

本文介绍了编程之美中关于将电话号码转换为对应英文单词的解题思路,从最初的冗余版本逐步优化,通过穷举方法解决未知长度数字的转换问题,并提供了测试结果。
摘要由CSDN通过智能技术生成

题干

对如非全键盘的手机上的数字,每个数字都对应一些字母,比如2对应ABC,3对应DEF.........,8对应TUV,9对应WXYZ,要求对一段数字,输出其代表的所有可能的字母组合,如5869,可能代表JTMW、JTMX.................

我的解题思路

刚开始我还以为本题会给我一个多么惊人的答案,结果发现,也只是穷举而已。尽管如此,本题虽然给了伪代码,但是还是比较难看懂,本着“纸上得来终觉浅 绝知此事要躬行”的要义,我决定自己做一遍。实际上刚开始也想了很久,总以为有什么技巧,哪里知道就是穷举而已。

难点就在于事先不知道输入多长的数字,如果只有两位,两个for循环,如果有三位,就三个for循环。但是在不知道的情况下,我设计出了我的解题思路,下面开始说明:

对于数字:23,共有9种可能(根据组合知识可知),如下所示

  1. ad
  2. ae
  3. af
  4. bd
  5. be
  6. bf
  7. cd
  8. ce
  9. cf

对于数字:234,共有下面27种可能,如下所示,

  1. adg
  2. adh
  3. adi
  4. aeg
  5. aeh
  6. aei
  7. afg
  8. afh
  9. afi
  10. bdg
  11. bdh
  12. bdi
  13. beg
  14. beh
  15. bei
  16. bfg
  17. bfh
  18. bfi
  19. cdg
  20. cdh
  21. cdi
  22. ceg
  23. ceh
  24. cei
  25. cfg
  26. cfh
  27. cfi
可以发现,在234中,由左到右的第三列直接,由上到下,只需要按照ghi的顺序一直重复27下,第二列则是3次d,3次e,3次f,而第一列则是9次a、9次b、9次c。这是有规律的。
规律就是:左到右的最后一列,只需要按照其数字代表的字母一直重复就可以了,而倒数第二列,重复的次数则是前一列数字代表的字符的个数,再下一列每个字符重复的次数就是前几列数字代表字符个数的乘积。按照这个规律,我写出了代码,并设计了测试用列。

成功而冗余的版本


#include <stdio.h>
#include <string.h>


void getwords(char *numstr){


	int num=atoi(numstr);   //把输入的字符转化为int类型

    if(num==0||num==1)return;	//可以返回一些没有意义一些情况


    //初始化的准备
	char c[10][10]={
	"",         //0
	"",         //1
	"abc",      //2
	"def",      //3
	"ghi",      //4
	"jkl",      //5
	"mno",      //6
	"pqrs",     //7
	"tuv",      //8
	"wxyz"      //9
	};
	int total[10]={0,0,3,3,3,3,3,4,3,4};


	int resultNum=1;    //我们要计算总共结果有多少个
	int count0And1=0;   //记录0和1的个数,因为0和1是没有字母打印的
	int length=0;
	while(num%10 || num/10){
		int temp=num%10;
		num/=10;

		if(temp==1||temp==0) {
			count0And1++;
			continue ;
		}
		resultNum*=total[temp];
		length++;
	}

	if(count0And1==length) return;	//全是没有1和0的情况
	char result[resultNum][length+1];    //用于存放结果的数组,注意0和1是不占位置的,加1是为了放置\0
	num=atoi(numstr);
	int h;		//h是循环变量

	for(h=0;h<resultNum;h++){
		result[h][length]='\0';  //给最后一位附上0
	}

	int count=1;	//这个的计算很复杂,它是当前要打印的一列的左边一共有多少种组合,也限制着当前这一列会要重复多少次其元素

	int k=0;			//k用来由最低位到最高位,设置当前应该打印一列的。

	for(h=0;h<length;++h){		//重复次数就是列数,不包括0和1的情况

		int lastPosition=num%10;    //拿到的是最低位的数字,我们也先放至最低位的字母
		num/=10;

		if(lastPosition==1||lastPosition==0) {  //如果这一位是0或者1,是不会产生字母的
			--h;
			continue ;

		}else{
			++k;								//如果是0或者1,应该不该打印这一列,所以不要移动k
		}

		int i,j;
		for(j=0,i=-1;j<resultNum;j++){

			if(j%count==0){		//同一个元素重复多少次
					++i;
				}
			if(i==total[lastPosition]) i=0;

			result[j][length-k]=c[lastPosition][i];	//length是算了0或者1的占位的长度,但是0和1没有字符,那一列不打,k是用于用后往前倒计的

		}
		if(total[lastPosition]){
			count*=total[lastPosition];
		}
	}
	int i;
	printf("数字为:%s\n",numstr);
	for(i=0;i<resultNum;i++){
		printf("%d\t%s\n",i+1,result[i]);
	}
}


int main() {
	/*下面都是些测试用列*/
	getwords(" ");		//边界,有空格字符串 我是让其直接返回了
	getwords("");		//边界,没有字符串
	getwords("0");
	getwords("23");		//随意检测
    getwords("234");	//随意检测

    getwords("229");	//9是含有4个字母的
    getwords("922");	//9在首
    getwords("292");	//9在首

    getwords("122");	//1是没有包含字符的
    getwords("221");	//1是没有包含字符的
    getwords("212");	//1是没有包含字符的

    getwords("022");	//1是没有包含字符的 这里出现的一个bug很有意思,是因为0在最前面,当变成int的时候,这个0就消失了,但是strlen还算会算这个0的占位
    getwords("220");	//1是没有包含字符的
    getwords("202");	//1是没有包含字符的
}

改进后的版本

/**
 * 改进版本有一个关键的想法就是:
 * 在传给getwords的字符串中先删除0和1这两个数,
 * 这两个数无论存在在哪里都不会影响结果,
 * 却会使代码需要写的更为复杂
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
 * 传入一个数字的字符串
 * 会打印该数字在手机键盘上出现的字母的组合
 */
void getwords(char *numstr){
	char *number=malloc(1);
	size_t i,j,h;//循环用的变量

	for(j=0,i=0;j<strlen(numstr);j++){
		if(numstr[j]!='1' &&numstr[j]!='0'){
			number[i]=numstr[j];
			++i;
		}
	}
	number[i]='\0';
	int num=atoi(number);   //把输入的字符转化为int类型
    if(num==0||num==1) return;	//可以返回一些没有意义一些情况
    //初始化的准备
	char c[10][10]={
	"",         //0
	"",         //1
	"abc",      //2
	"def",      //3
	"ghi",      //4
	"jkl",      //5
	"mno",      //6
	"pqrs",     //7
	"tuv",      //8
	"wxyz"      //9
	};
	int total[10]={0,0,3,3,3,3,3,4,3,4};

	int resultNum=1;    //我们要计算总共结果有多少个
	int length=0;
	while(num%10){
		int temp=num%10;
		num/=10;
		resultNum*=total[temp];
		length++;
	}
	char result[resultNum][length+1];    //加一是加‘\0’的位置
	num=atoi(number);	//刚刚num被计算过来,这次再次拿到其值

	//给最后一位附上0
	for(h=0;h<resultNum;h++){
		result[h][length]='\0';
	}

	int count=1;	//它是当前要打印的一列的左边一共有多少种组合,也限制着当前这一列会要重复多少次其元素
	int k=0;			//k用来由最低位到最高位,设置当前应该打印一列
	for(h=0;h<length;++h){		//重复次数就是列数
		int lastPosition=num%10;    //拿到的是最低位的数字,我们也先放至最低位的字母
		num/=10;
		++k;
		int i,j;
		for(j=0,i=-1;j<resultNum;j++){

			if(j%count==0){		//同一个元素重复多少次
					++i;
				}
			if(i==total[lastPosition]) i=0;

			result[j][length-k]=c[lastPosition][i];	//length是算了0或者1的占位的长度,但是0和1没有字符,那一列不打,k是用于用后往前倒计的

		}
		if(total[lastPosition]){
			count*=total[lastPosition];//计算打印了这一列有多少个,会影响下一个循环重复多少次
		}
	}
	//打印结果
	printf("数字为:%s\n",numstr);
	for(i=0;i<resultNum;i++){
		printf("%d\t%s\n",i+1,result[i]);
	}
}


int main() {
	/*下面都是些测试用列*/
	getwords(" ");		//边界,有空格字符串 我是让其直接返回了
	getwords("");		//边界,没有字符串
	getwords("0");
	getwords("23");		//随意检测
    getwords("234");	//随意检测

    getwords("229");	//9是含有4个字母的
    getwords("922");	//9在首
    getwords("292");	//9在首

    getwords("122");	//1是没有包含字符的
    getwords("221");	//1是没有包含字符的
    getwords("212");	//1是没有包含字符的

    getwords("022");	//0是没有包含字符的 并且0这个数字有点特别会影响计算
    getwords("220");	//0是没有包含字符的
    getwords("202");	//0是没有包含字符的
}

测试结果

asd@asd-desktop:~/workspace/test/src$ ./a.out 
数字为:23
1	ad
2	ae
3	af
4	bd
5	be
6	bf
7	cd
8	ce
9	cf
数字为:234
1	adg
2	adh
3	adi
4	aeg
5	aeh
6	aei
7	afg
8	afh
9	afi
10	bdg
11	bdh
12	bdi
13	beg
14	beh
15	bei
16	bfg
17	bfh
18	bfi
19	cdg
20	cdh
21	cdi
22	ceg
23	ceh
24	cei
25	cfg
26	cfh
27	cfi
数字为:229
1	aaw
2	aax
3	aay
4	aaz
5	abw
6	abx
7	aby
8	abz
9	acw
10	acx
11	acy
12	acz
13	baw
14	bax
15	bay
16	baz
17	bbw
18	bbx
19	bby
20	bbz
21	bcw
22	bcx
23	bcy
24	bcz
25	caw
26	cax
27	cay
28	caz
29	cbw
30	cbx
31	cby
32	cbz
33	ccw
34	ccx
35	ccy
36	ccz
数字为:922
1	waa
2	wab
3	wac
4	wba
5	wbb
6	wbc
7	wca
8	wcb
9	wcc
10	xaa
11	xab
12	xac
13	xba
14	xbb
15	xbc
16	xca
17	xcb
18	xcc
19	yaa
20	yab
21	yac
22	yba
23	ybb
24	ybc
25	yca
26	ycb
27	ycc
28	zaa
29	zab
30	zac
31	zba
32	zbb
33	zbc
34	zca
35	zcb
36	zcc
数字为:292
1	awa
2	awb
3	awc
4	axa
5	axb
6	axc
7	aya
8	ayb
9	ayc
10	aza
11	azb
12	azc
13	bwa
14	bwb
15	bwc
16	bxa
17	bxb
18	bxc
19	bya
20	byb
21	byc
22	bza
23	bzb
24	bzc
25	cwa
26	cwb
27	cwc
28	cxa
29	cxb
30	cxc
31	cya
32	cyb
33	cyc
34	cza
35	czb
36	czc
数字为:122
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc
数字为:221
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc
数字为:212
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc
数字为:022
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc
数字为:220
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc
数字为:202
1	aa
2	ab
3	ac
4	ba
5	bb
6	bc
7	ca
8	cb
9	cc

书上while循环完成的代码

/**
 * 根据书上221页的伪代码写成。
 * 调试着慢慢看吧,两个while循环确实配合的非常好
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void getwords(char *numstr){
	 int TelLength=strlen(numstr);
	 int number[TelLength];
	 int answer[TelLength];
	 int i;
	 for(i=0;i<TelLength;i++){
		 answer[i]=0;
		 number[i]=numstr[i]-'0';//把char类型数字转化为int类型
	 }
    //初始化的准备
	char c[10][10]={
	"",         //0
	"",         //1
	"abc",      //2
	"def",      //3
	"ghi",      //4
	"jkl",      //5
	"mno",      //6
	"pqrs",     //7
	"tuv",      //8
	"wxyz"      //9
	};
	int total[10]={0,0,3,3,3,3,3,4,3,4};
	int n=TelLength;//n为电话号码的位数
	while(1){
		int i;
		for(i=0;i<TelLength;i++){
			printf("%c",c[number[i]][answer[i]]);
		}
		printf("\n");
		int k=n-1;
		while(k>=0){
			if(answer[k]<total[number[k]]-1){
				answer[k]++;
				break;
			}else{
				answer[k]=0;k--;
			}
		}
		if(k<0){
			break;
		}
	}
}


int main() {
	getwords("234");
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值