编程之美读书笔记3.2—电话号码对应英语单词

1. 简述

    电话的号码盘一般可以用于输入字母。如用2可以输入A、B、C,用3可以输入D、E、F等。
    对于号码5869872,可以依次输出其代表的所有字母组合。如:JTMWTPA、JTMWTB······
    1) 设计程序,尽可能从这些字母组合中,找到一个有意义的单词来表述一个电话号码。如单词"computer"来描述号码26678837。
    2) 对于一个号码,是否可以用一个单词代表?怎样才是最快的方法?

2. 思路

    由于两个任务都需要判断一个字母组合是否是一个单词,所以必须预先存一份单词列表。
    方法一:直接对号码进行深度优先搜索,空间基本上没怎么用,就是时间太大了。假设,单词列表长度为N,号码K位,一个按键3个字符(实际上,不是每个数字对应的字符个数都一样),这就是3^k种字符序列。每找到一个序列,还要去单词列表里面查找,O(N),这还没算每次字符序列和单词匹配的具体时间,总之时间复杂度很高。
    方法二:空间换时间,这就有两个具体思路。
    一个是从号码这边走,把每个号码映射为一个数字,然后把每个数字对应的字符串找到,再根据单词列表,找到单词,实际上是把方法1中的任务离线做好了。这样有一个问题一般手机都有11位(每位假设都可以是0-9),这就是10^11种,100G个数字,每种数字再用方法一的方法进行操作,复杂度可想而知。
    另一个方法是从单词这边走,实际上,英语单词个数是有限的,常用单词也就几千,考GRE也就几万吧,假设10万个单词,我们找到每个单词对应的号码,也是10万。对这10万个单词可以用Lucene建个索引,或者搞个字典树,对于索引,每个号码挂上对应单词在单词列表中的下标,对于字典树,每个号码节点挂上对应单词在单词列表中的下标。
    这样来个一个号码,找到这个号码是否存在于索引和字典树的复杂度基本上就是常数级别,如果找到了,就能找到对应单词在单词列表中下标,进一步找到单词,如果找不到号码,就说明没有对应单词。

3.

<pre name="code" class="cpp">#include<iostream>
#define TelLength 3

using namespace std;



//将各个数字多能代表的字符存储在一个二维数组中
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 number[TelLength];//TelLength为电话号码的位数
//将数组目前能代表的字符在其所能代表的字符集中的位置用一个数组存储起来
int answer[TelLength];//初始化answer[i]=0
/*举个例子,若number[0]=4,即电话号码的第一位为4,若answer[0]=2,即4目前所代表的字符为
c[number[0]][answer[0]]=c[4][2]='I'*/

//method1直接循环法
//假设电话号码只有3位,那么很快会写出3个for循环来
void directCycle()
{
	for(answer[0]=0;answer[0]<total[number[0]];answer[0]++)
		for(answer[1]=0;answer[1]<total[number[1]];answer[1]++)
			for(answer[2]=0;answer[2]<total[number[2]];answer[2]++)
			{
				for(int i=0;i<3;i++)cout<<c[number[i]][answer[i]];
				cout<<endl;
			}
}

/*
的确,针对3位的电话号码,此3个for循环可以很好地解决问题,但是不同地区的电话号码位数不同,而且若是电话号码位数升级
了呢?那我们就必须要修改源代码去增加若干个for循环,这是一件很痛苦的事情,而且也体现不出编程之“美”来,一下对程序
做下简单修改,即可解决这样的可扩展问题
*/
//method2
void directSearch()
{
	while(true)  
	{  
		for(int i = 0; i < TelLength; i++)//每当有一个数字改变了,都是一种排列,直接输出  
			printf("%c", c[number[i]][answer[i]]);  
		printf("\n");  

		int k = TelLength- 1;//其中某一位改变后,都从最后一个数开始重新赋值,形成新排列  
		while(k >= 0)  
		{  
			if(answer[k] < total[number[k]] - 1)//其中某一位改变后,也是一种新排列,不再继续循环  
			{  
				answer[k]++;  
				break;  
			}  
			else//第k位的数已经全部放完,此时需要设置第k - 1位。  
			{  
				answer[k] = 0;//为了下一次下标++后不越界,需要把第k位置0  
				k--;  
			}  
		}  
		if(k < 0)//所有元素均处理完了。  
			break;  
	}  
}

//method3
/*
index说明对电话号码的第几位进行循环
*/
void RecursiveSearch(int index)
{
	if(index==TelLength)    //递归出口,当index为TelLength时,打印本次的TelLength次号码
	{
		for(int i=0;i<TelLength;i++)
		{
			cout<<c[number[i]][answer[i]];
		}
		cout<<endl;
		return ;
	}
	for(answer[index]=0;answer[index]<total[number[index]];answer[index]++)
	{
		RecursiveSearch(index+1);
	}
}

void init()
{
	for(int i=0;i<TelLength;i++)answer[i]=0;
	for(int i=0;i<TelLength;i++)cin>>number[i];
}
int main()
{
	init();
	//directCycle();
	directSearch();
	//RecursiveSearch(0);
	system("pause");
	return 0;
}


                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值