今天晚上参加了网新国际2008春季招聘的笔试。第一个编程题花了我好长时间。
题目大致是这样的:在电话或者手机上,一个数字如2对应着字母ABC,7对应着PQRS。那么数字串27所对应的字符的可能组合就有3*4=12种(如AP,BR等)。现在输入一个3到11位长的电话号码,请打印出这个电话号码所对应的字符的所有可能组合和组合数。如果数字串中有0或者1,直接返回0。编程请考虑效率,健壮等问题。
我的解法和8皇后的递归回溯解法是一致的。我把每个数字看成8皇后棋盘中的一行,这一行的内容就是这个数字所对应的字符串。和8皇后问题从每一行中选一个列位置类似的,我在每一个数字对应的字符串中选一个字符,然后递归进行直到找到一个可能字符组合为止。
下面是我的代码:
#include <stdio.h>
#include <string.h> // 全局变量。
char *pStr[] = {"ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"};
char num_str[12]; // 储存输入的电话号码所对应的字符串。
char result[12]; // 储存一个可能的字符组合。
char str_idx[12]; // 储存输入的电话号码的数字所对应的字符串在pStr中的索引。
int cnt_zuhe = 0; // 储存可能的字符组合总数。
int num_len; // 储存输入的电话号码的长度。
void reverse(char *pstr)
{
int i = 0, j = strlen(pstr) - 1;
char tmp;
while (i < j ) {
tmp = pstr[i];
pstr[i] = pstr[j];
pstr[j] = tmp;
++i;
--j;
}
} // 将数字转换为字符串。
int get_num_str(int number, char* str)
{
int cnt = 0;
do {
str[cnt++] = (char)(number % 10 + '/0');
} while (number /= 10);
str[cnt] = '/0';
num_len = cnt;
} void init_idxs()
{
int i = 0;
char *p = num_str;
while (*p != '0') {
str_idx[i++] = *p - '0' - 2;
++p;
}
} bool IsLegal(int i, int j)
{
if (i < num_len && pStr[str_idx[i]][j] != '/0')
return true;
return false;
} void f(int i)
{
// i为输入的电话号码中的第i个数字,j为这个数字对应的字符串的第j个字符。
for (int j = 0; j != 4; ++j) {
// 取第j个字符。如果索引i和j都合法,则把字符加入到结果中。
if (IsLegal(i, j)) {
result[i] = pStr[str_idx[i]][j];
if (i == num_len - 1) {
++cnt_zuhe;
printf("%s ", result);
}
// 第i + 1个数字。
f(i + 1);
}
}
} int transfer(int number)
{
// 得到电话号码对应的字符串。
num_len = get_num_str(number, num_str);
if (num_len < 3 || num_len > 11)
return 0;
char *p = num_str;
while (*p != '/0') {
if (*p == '0' || *p == '1')
return 0;
++p;
}
init_idxs();
// 从电话号码的第0个数字开始搜索。
f(0);
return cnt_zuhe;
} int main()
{
int number;
scanf("%d", &number);
printf("%d ", transfer(number));
return 0;
}
#include <string.h> // 全局变量。
char *pStr[] = {"ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"};
char num_str[12]; // 储存输入的电话号码所对应的字符串。
char result[12]; // 储存一个可能的字符组合。
char str_idx[12]; // 储存输入的电话号码的数字所对应的字符串在pStr中的索引。
int cnt_zuhe = 0; // 储存可能的字符组合总数。
int num_len; // 储存输入的电话号码的长度。
void reverse(char *pstr)
{
int i = 0, j = strlen(pstr) - 1;
char tmp;
while (i < j ) {
tmp = pstr[i];
pstr[i] = pstr[j];
pstr[j] = tmp;
++i;
--j;
}
} // 将数字转换为字符串。
int get_num_str(int number, char* str)
{
int cnt = 0;
do {
str[cnt++] = (char)(number % 10 + '/0');
} while (number /= 10);
str[cnt] = '/0';
num_len = cnt;
reverse(str);
return cnt;
} void init_idxs()
{
int i = 0;
char *p = num_str;
while (*p != '0') {
str_idx[i++] = *p - '0' - 2;
++p;
}
} bool IsLegal(int i, int j)
{
if (i < num_len && pStr[str_idx[i]][j] != '/0')
return true;
return false;
} void f(int i)
{
// i为输入的电话号码中的第i个数字,j为这个数字对应的字符串的第j个字符。
for (int j = 0; j != 4; ++j) {
// 取第j个字符。如果索引i和j都合法,则把字符加入到结果中。
if (IsLegal(i, j)) {
result[i] = pStr[str_idx[i]][j];
if (i == num_len - 1) {
++cnt_zuhe;
printf("%s ", result);
}
// 第i + 1个数字。
f(i + 1);
}
}
} int transfer(int number)
{
// 得到电话号码对应的字符串。
num_len = get_num_str(number, num_str);
// 检查输入是否符合要求。
if (num_len < 3 || num_len > 11)
return 0;
char *p = num_str;
while (*p != '/0') {
if (*p == '0' || *p == '1')
return 0;
++p;
}
// 计算输入的电话号码的数字所对应的字符串在pStr中的索引。
init_idxs();
// 从电话号码的第0个数字开始搜索。
f(0);
return cnt_zuhe;
} int main()
{
int number;
scanf("%d", &number);
printf("%d ", transfer(number));
return 0;
}
我的一个同学的代码。他用的是循环的方式。用一个数组index记录当前每个数字对应的字符串中应该输出的那个字符的在字符串中的索引。穷举所有可能的索引情况,并打印出来。穷举的过程是总是在index数组最末尾加一,并在需要时向前进位。
#include <iostream>
using namespace std; const char str[10][5]= {"", "", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"};
const int size[10] = {0, 0, 3, 3, 3, 3, 3, 4, 3, 4};
int index[11]; // index[i]表示:和第i个数字对应应该输出字符串的第几个字母
int digit[11]; // 把number转化成数字后存在里面
int func(int number)
{
if (number < 0)
return 0;
// 把数字拆开并记录。这里拆开后没有反过来,而是在后面打印的时候反过来打印,不错!
int cnt = 0;
while (number)
{
digit[cnt] = number % 10;
// 遇见0或者1,就直接返回。
if (digit[cnt] == 1 || digit[cnt] == 0)
return 0;
number /= 10;
++cnt;
}
int len = cnt; // 记录数字有多少长度
int curCount = 0; // 用来表示输出字母组合的当前个数
memset(index, 0, sizeof(index)); // 初始化时都为0
while (true) {
// 根据index数组中的计算所得的每个数字对应的字符串应该输出的字母的索引输出当前字符组合。
for (int i = len - 1; i >= 0; i--)
cout << str[digit[i]][index[i]];
cout << endl;
int k = len - 1;
if (index[k] < size[digit[k]] - 1) // 注意数组下标从0开始。
++index[k];
else {
// 用一个while循环来向前进位。程序写的巧妙。
++index[k];
while (index[k] == size[digit[k]]) {
index[k] = 0;
--k;
++index[k];
}
}
if (k == -1) // 现在index的各个元素都变为零了。
break;
/*
int k = len - 1;
while (k >= 0) {
if (index[k] < size[digit[k]] - 1) { // digit[k] - 1
++index[k];
break;
} else {
index[k] = 0;
--k;
}
}
if (k < 0)
break;
*/
}
return curCount;
} int main()
{
int number;
while (scanf("%d", &number))
printf("%d ", func(number));
return 0;
}
using namespace std; const char str[10][5]= {"", "", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"};
const int size[10] = {0, 0, 3, 3, 3, 3, 3, 4, 3, 4};
int index[11]; // index[i]表示:和第i个数字对应应该输出字符串的第几个字母
int digit[11]; // 把number转化成数字后存在里面
int func(int number)
{
if (number < 0)
return 0;
// 把数字拆开并记录。这里拆开后没有反过来,而是在后面打印的时候反过来打印,不错!
int cnt = 0;
while (number)
{
digit[cnt] = number % 10;
// 遇见0或者1,就直接返回。
if (digit[cnt] == 1 || digit[cnt] == 0)
return 0;
number /= 10;
++cnt;
}
int len = cnt; // 记录数字有多少长度
int curCount = 0; // 用来表示输出字母组合的当前个数
memset(index, 0, sizeof(index)); // 初始化时都为0
while (true) {
// 根据index数组中的计算所得的每个数字对应的字符串应该输出的字母的索引输出当前字符组合。
for (int i = len - 1; i >= 0; i--)
cout << str[digit[i]][index[i]];
cout << endl;
++curCount;
// 每次都从index[len - 1]增一。
int k = len - 1;
if (index[k] < size[digit[k]] - 1) // 注意数组下标从0开始。
++index[k];
else {
// 用一个while循环来向前进位。程序写的巧妙。
++index[k];
while (index[k] == size[digit[k]]) {
index[k] = 0;
--k;
++index[k];
}
}
if (k == -1) // 现在index的各个元素都变为零了。
break;
// 《编程之美》上的写法。更加精妙。
/*
int k = len - 1;
while (k >= 0) {
if (index[k] < size[digit[k]] - 1) { // digit[k] - 1
++index[k];
break;
} else {
index[k] = 0;
--k;
}
}
if (k < 0)
break;
*/
}
return curCount;
} int main()
{
int number;
while (scanf("%d", &number))
printf("%d ", func(number));
return 0;
}