|
google 竞赛题 SecretSum 的 C++ 解法 下载源代码 int i,j,count=0;
for ( i = 100; i<=899; i++)
{
for ( j = 100; j <= 999-i; j++)
{
if ( 匹配成功)
{
count++;
}
}
}
这么一来需要判断的组合数量变成 800+799+......+1 =320400(种) 比原先减少了近一半。但数量依旧庞大。继续省题,我从"ABA"不可以代表555的说明中得到提示:在得到一个三位数时,就可以根据不同字母取不同的值来筛掉一批不符合要求的数。怎么实现这种比较呢?我是这么做的,将一个三位数(或字符串)中每一位置的数(或字母)与其他位置的数(或字母)比较,记下比较的结果保存在一个整型数组中,1表示比较结果相同,0表示不相同。还好字符串才3个字母,各个位置的比较总共才三次,分别为(0,1),(0,2),(2,1),例如在"ABA"的比较中:0位置的A和1位置的B比较,显然不等结果为0;0位置的A和2位置的A比较,相等结果为1;1位置的B和2位置的A比较,结果不等为0.结果可以用一个大小为3的数组保存."ABA"的比较结果为{0,1,0};同样的方法来计算929的比较结果为{0,1,0};和"ABA"结果相同,所以"ABA"可以代表929.而555比较结果为{1,1,1},所以"ABA"不可能代表555.由于传入的字符串有3个,我们可以使用3*3大小来保存这个结果.在类中我用int m_linePattern[3][3];这个成员变量来表示.在循环筛选前,应该先把传入的字符串匹配类型计算出来.我添加了成员函数void init(string **pStr)来完成此工作.代码如下: void init(string **pStr)
{
int i,j;
memset(m_linePattern,0,sizeof(m_linePattern));
for ( i = 0 ; i< 3; i++)
{
char ch[3];
for ( j = 0; j<3; j++)
{
ch[j] = (*pStr[i])[j];
}
if ( ch[0]==ch[1]) m_linePattern[i][0]=1;//比较0,1位置
if ( ch[0]==ch[2]) m_linePattern[i][1]=1;//比较0,2位置
if ( ch[1]==ch[2]) m_linePattern[i][2]=1;//比较1,2位置
}
}
我又添加了成员函数int IfNumOk(int idx, int num)来比较num是否符合索引为idx的字符串.匹配成功返回1,失败返回0.代码如下: int IfNumOk(int idx, int num)
{
char temp[4];
int com[3]={0};
sprintf(temp,"%d",num);//将num转换成字符串
//计算传入数字的匹配结果
if (temp[0] == temp[1] ) com[0]=1;
if (temp[0] == temp[2] ) com[1]=1;
if (temp[2] == temp[1] ) com[2]=1;
//与索引为idx的字符串匹配结果比较
for ( int i=0; i< 3; i++)
if ( com[i] != m_linePattern[idx][i]) return 0;
return 1;
} 这样同时通过了IfNumOk筛选的数字才正式进入最后的比较.我添加了函数int IfMatch(int *maybe)来做判断,符合要求时返回1,不符合返回0.至此函数countPossible的代码已可全部确定下来了,代码如下: int countPossible(string num1, string num2, string result)
{
string *pStr[]={&num1,&num2,&result};
init(pStr);
int i,j,count=0;
for ( i = 100; i<=899; i++)
{
if ( ! IfNumOk(0, i)) continue;
for ( j = 100; j <= 999-i; j++)
{
if ( ! IfNumOk(1, j)) continue;
int maybe[]={i,j,i+j};
if ( ! IfNumOk(2, maybe[2])) continue;
if ( IfMatch (maybe))
{
count++;
}
}
}
return count;
} 通过上述方法筛选出的3个候选数时,当字符串中相同字母越多,则符合的数越少.例如当三个字母都相等时,通过筛选的num1,num2的数各只有8个,显然计算速度大大加快.即使最糟糕的情况下num1,num2和result中三个字母各不相同,则通过筛选的组合有166176种,还是远少于优化前的320400(种)了. 2.3 判断候选的3个数是否匹配传入的字符串 我是这样判断的:首先在候选的3个数中,在相同字母出现的对应位置的数必须相同.其次,该数字必须是未被使用过的。 后者的实现很简单,添加一个变量 int bUse[10]={0};。bUse[i](i=0-9)的值表示数字i被使用的情况,0表示未被使用过;1表示已经使用过了。初始化时都为0表示都未被使用过。当一个字母出现的对应位置的数都相同时,若这个数字为i,则bUse[i]=1。 在前者的处理中,为了确定位置,我将传入的三个字符串看成一个2维的字母矩阵。num1,num2,result对应的一维下标值分别为0,1,2。该矩阵的第1维下标值和第2维下标值就可看成矩阵中字母的坐标。例如在例1中字母A的坐标为(0,0);D点的坐标为(1,0)。同样的方法也可为候选的3个数中的每个数字确定坐标。我添加了一个结构来保存上述的坐标: typedef struct{
int dim1;
int dim2;
}POS;
其中dim1表示一个字母(数字)的第1维下标值。同理,dim2表示一个字母(数字)的第2维下标值。一个字母可能在多个位置出现,所以我添加了个成员变量来保存每个字母在矩阵中出现的位置: typedef vector<POS> POSVEC; POSVEC m_letterPos[26]; //保存每个字母的出现位置向量的数组其中m_letterPos[0]保存字母A出现的位置,m_letterPos[1]保存字母B出现的位置,以此类推m_letterPos[25]保存字母Z出现的位置。在字母矩阵中显然许多字母的位置向量为空,空向量对接下去的筛选判断根本没有用处,所以我又添加了个成员变量,来保存不为空的字母位置向量的指针 typedef vector<POSVEC*> POSPTRVEC; POSPTRVEC m_posptrVec; //保存出现的字母向量的指针显然上述2个成员变量同样需要在循环筛选前初始化,代码同样添加到成员函数init中。添加后init代码如下: void init(string **pStr)
{
int i,j;
memset(m_linePattern,0,sizeof(m_linePattern));
for ( i = 0 ; i< 3; i++)
{
char ch[3];
for ( j = 0; j<3; j++)
{
ch[j] = (*pStr[i])[j];
POS ps={i,j};
m_letterPos[ch[j]-''A''].push_back(ps);//将坐标保存到相应向量中
}
//计算各个字母的匹配矩阵
if ( ch[0]==ch[1]) m_linePattern[i][0]=1;
if ( ch[0]==ch[2]) m_linePattern[i][1]=1;
if ( ch[1]==ch[2]) m_linePattern[i][2]=1;
}
for ( i = 0; i<26; i++)
{
if (m_letterPos[i].size()>0)
m_posptrVec.push_back(&m_letterPos[i]);//将所有出现的字母向量指针保存起来
}
}
具体判断候选的3个数字是否匹配时,先将这3个数字转换到3个字符串数组中去,这个转换可以使用sprintf函数。然后遍历m_posptrVec中非空的位置向量,判断每个保存在相同字母的位置向量中的位置,在相应的候选数字矩阵的相应位置的数是否相等,若相等则设置该数字以被使用,具体的IfMatch代码如下: int IfMatch(int *maybe)
{
char numChar[3][4];
int bUse[10]={0};
int i,j;
for ( i = 0 ; i <3; i++)
sprintf(numChar[i],"%d",maybe[i]);//将数字转换成字符串
for ( i = 0; i < m_posptrVec.size(); i++)//遍历所有出现的字母
{
POSVEC &ps= (*m_posptrVec[i]);
int ch = numChar[ps[0].dim1][ps[0].dim2]-''0'';//取得该字母第一个位置处的数字
if ( bUse[ch] ) return 0; //如果数字已被使用就返回0
//逐一比较其他位置出的数字是否都相同
j=1;
while( j < ps.size() )
{
int ch2 = numChar[ps[j].dim1][ps[j].dim2]-''0'';
if (ch != ch2 ) return 0;
j++;
}
bUse[ch] = 1;//将该数字设置为已被使用
}
return 1;
}
2.4.最后将这个类的实现代码汇总如下: #include <string>
#include <vector>
#include <stdlib.h>
#include <iostream>
using namespace std;
//****************************************************************
//类名:SecretSum
//作者:roc(txqc4@sohu.com)
//日期:2005-12-26
//用途: 本代码为实现上述竞赛题所作。
//注意事项:如欲转载,敬请保持本段说明。
//****************************************************************
class SecretSum{
typedef struct{
int dim1;
int dim2;
}POS;
typedef vector<POS> POSVEC;
typedef vector<POSVEC*> POSPTRVEC;
public :
int countPossible(string num1, string num2, string result)
{
string *pStr[]={&num1,&num2,&result};
init(pStr);
int i,j,count=0;
for ( i = 100; i<=899; i++)
{
if ( ! IfNumOk(0, i)) continue;
for ( j = 100; j <= 999-i; j++)
{
if ( ! IfNumOk(1, j)) continue;
int maybe[]={i,j,i+j};
if ( ! IfNumOk(2, maybe[2])) continue;
if ( IfMatch (maybe))
{
count++;
}
}
}
return count;
}
void init(string **pStr)
{
int i,j;
memset(m_linePattern,0,sizeof(m_linePattern));
for ( i = 0 ; i< 3; i++)
{
char ch[3];
for ( j = 0; j<3; j++)
{
ch[j] = (*pStr[i])[j];
POS ps={i,j};
m_letterPos[ch[j]-''A''].push_back(ps);//将坐标保存到相应向量中
}
//计算各个字母的匹配矩阵
if ( ch[0]==ch[1]) m_linePattern[i][0]=1;
if ( ch[0]==ch[2]) m_linePattern[i][1]=1;
if ( ch[1]==ch[2]) m_linePattern[i][2]=1;
}
for ( i = 0; i<26; i++)
{
if (m_letterPos[i].size()>0)
m_posptrVec.push_back(&m_letterPos[i]);//将所有出现的字母向量指针保存起来
}
}
int IfMatch(int *maybe)
{
char numChar[3][4];
int bUse[10]={0};
int i,j;
for ( i = 0 ; i <3; i++)
sprintf(numChar[i],"%d",maybe[i]);//将数字转换成字符串
for ( i = 0; i < m_posptrVec.size(); i++)//遍历所有出现的字母
{
POSVEC &ps= (*m_posptrVec[i]);
int ch = numChar[ps[0].dim1][ps[0].dim2]-''0'';//取得该字母第一个位置处的数字
if ( bUse[ch] ) return 0; //如果数字已被使用就返回0
//逐一比较其他位置出的数字是否都相同
j=1;
while( j < ps.size() )
{
int ch2 = numChar[ps[j].dim1][ps[j].dim2]-''0'';
if (ch != ch2 ) return 0;
j++;
}
bUse[ch] = 1;//将该数字设置为已被使用
}
return 1;
}
int IfNumOk(int idx, int num)
{
char temp[4];
int com[3]={0};
sprintf(temp,"%d",num);//将数字转换成字符串
//计算传入数字的匹配结果
if (temp[0] == temp[1] ) com[0]=1;
if (temp[0] == temp[2] ) com[1]=1;
if (temp[2] == temp[1] ) com[2]=1;
//与索引为idx的字符串匹配结果比较
for ( int i=0; i< 3; i++)
if ( com[i] != m_linePattern[idx][i]) return 0;
return 1;
}
protected:
POSVEC m_letterPos[26]; //保存每个字母的出现位置向量的数组
POSPTRVEC m_posptrVec; //保存出现的字母向量的指针
int m_linePattern[3][3];//保存每个字符串匹配类型的数组
};
三、小结使用上述的代码对题中给出7个实例进行实测时,我发现例5的计算过程最慢。一分析发现原来例5中的num1,num2,result,都是{0,0,0}型的,这样最终IfMatch被调用了117662次。而在例4中num1和num2同样是{0,0,0}型的,但由于result为{1,1,1}型,事实上IfMatch最终只被调用了2144次,计算用时尚能忍受。由此可见,我的代码对于num1,num2,都是{0,0,0}型的计算还有优化的余地,限于时间和精力我没有继续优化下去。欢迎各位高手,共同探讨,给出更优的解决方案。 |
背景:
阅读新闻
google 竞赛题 SecretSum 的 C++ 解法
| [日期:2006-11-13] | 作者: | [字体:大 中 小] |
阅读: 次
【 打印 】
【 打印 】
全站导航
gmail.com