前阵子参加了Google的code jam,没有编程功底的人果然过不了第一关,不过事后重新做做还是挺有意思的,例如第一轮第三场的第二题,题目如下:
小明玩个游戏,一开始有N个火车车厢,每个车厢里都有字母,现在要组装火车,要求把所有车厢连在一起组成字符串,这个字符串要求相同的字母只能相邻,问现在有几种组装的方式。例如有三个车厢a,a,ab,组装的方式就有123,213两种。
题目讲完,现在来分析题目,先来考虑一个简单的情况,各个车厢的字母都是不一样的,相互独立,那么这时可拼装的方式一共有N!种,这也是题目最大的组装方式数。那么,我们需要把所有车厢的状态转化成上述独立状态,为了达到以上要求,需要首先处理每个车厢连续重复的字母,方便统计,然后处理所有单个字母,把所有相同的单个字母合并在一起,此时合并后的车厢内部可交换方案数目为字母数的阶乘,接着把单个字母和其他有相同结尾或开头的多个字母合并,合并后的车厢内部可交换方案数目为两个车厢可交换数的乘积,这样一来,原来单个字母的车厢要么已经合并,要么就跟其他所有车厢的字母都不一样,处理完单个字母,最后再把多个字母的车厢合并,合并后的车厢内部可交换数目为合并前两个车厢可交换数的乘积。上述处理结束后,假设还剩M个车厢,各个车厢的内部可交换数为A1,A2,...,Am,那么最后火车组装的方式一共有M!*A1*A2*...*Am,需要注意的是,处理车厢前需要检查各个车厢是否满足相同字母只能相邻的条件,然后处理车厢结束后,检查拼在一起的一种方案中组成的字符串是否满足相同字母只能相邻的条件。
解题思路就讲到这里,下面就直接上代码吧。
首先车厢的数据结构,包括车厢字符串,字符串里单个字母的数量,内部可交换的数目,以及一个表示车厢是否被合并过的,是否有效的布尔型。
struct train{
bool isValid;
int oneNum;
unsigned long long exchange;
string name;
train():isValid(true),oneNum(0),exchange(1){};
};
然后就是合并单个字母函数
void mergeOneLetter(train* trainset,int len){
for (int i=0; i<len; i++) {
train& currentTrain=trainset[i];
if (currentTrain.isValid&¤tTrain.name.length()==1) {
//先把所有单个字符合并
for (int j=i+1; j<len; j++) {
train& otherTrain=trainset[j];
if (otherTrain.isValid&&otherTrain.name.length()==1&&otherTrain.name[0]==currentTrain.name[0]) {
currentTrain.oneNum++;
currentTrain.exchange*=currentTrain.oneNum;
otherTrain.isValid=false;
}
}
//再合并多个字符的,确保单个字符要么不能连,要么已经合并不再是单个字符,方便下一步
for (int j=0; j!=i&&j<len; j++) {
train& otherTrain=trainset[j];
if (otherTrain.isValid) {
if (otherTrain.name[0]==currentTrain.name[0]) {
otherTrain.isValid=false;
stringstream mergeStream;
mergeStream<<currentTrain.name<<otherTrain.name;
currentTrain.name=mergeStream.str();
currentTrain.exchange*=otherTrain.exchange;
break;
}
if (otherTrain.name[otherTrain.name.length()-1]==currentTrain.name[0]) {
otherTrain.isValid=false;
stringstream mergeStream;
mergeStream<<otherTrain.name<<currentTrain.name;
currentTrain.name=mergeStream.str();
currentTrain.exchange*=otherTrain.exchange;
break;
}
}
}
}
}
}
接着是合并其他车厢的函数
//合并所有火车
void mergeAllTrain(train* trainset,int len){
for (int i=0; i<len; i++) {
train& currentTrain=trainset[i];
if (currentTrain.isValid) {
currentTrain.isValid=false;
char firstChar=currentTrain.name[0],lastChar=currentTrain.name[currentTrain.name.length()-1];
int firstOccur=-1,lastOccur=-1;
while ((firstOccur=findFirstLetter(trainset, len, lastChar))!=-1) {
train& headTrain=trainset[firstOccur];
headTrain.isValid=false;
stringstream mergeStream;
mergeStream<<currentTrain.name<<headTrain.name;
currentTrain.name=mergeStream.str();
currentTrain.exchange*=headTrain.exchange;
lastChar=currentTrain.name[currentTrain.name.length()-1];
}
while ((lastOccur=findLastLetter(trainset, len, firstChar))!=-1) {
train& tailTrain=trainset[lastOccur];
tailTrain.isValid=false;
stringstream mergeStream;
mergeStream<<tailTrain.name<<currentTrain.name;
currentTrain.name=mergeStream.str();
currentTrain.exchange*=tailTrain.exchange;
firstChar=currentTrain.name[0];
}
currentTrain.isValid=true;
}
}
}
附上判断一个车厢是否满足相同字母只能相邻的代码,判断整列火车方法也类似。
//判断一个train是否有效
bool judgeOneTrain(const train& theTrain){
set<char> occurSet;
string testString=theTrain.name;
occurSet.insert(testString[0]);
for (int i=1; i<testString.length(); i++) {
char letter=testString[i];
if(letter!=testString[i-1]){
if (occurSet.find(letter)!=occurSet.end()) {
return false;
}
else{
occurSet.insert(letter);
}
}
}
return true;
}
最后,这道题我只过了小数据,因为要过大数据,涉及大数阶乘,要用数组辅助,我也不想做了。