一道Google top coder的850分例题及解答
原题:
假设有这样一种字符串,它们的长度不大于 26 ,而且若一个这样的字符串其长度为 m ,则这个字符串必定由 a, b, c ... z 中的前 m 个字母构成,同时我们保证每个字母出现且仅出现一次。比方说某个字符串长度为 5 ,那么它一定是由 a, b, c, d, e 这 5 个字母构成,不会多一个也不会少一个。嗯嗯,这样一来,一旦长度确定,这个字符串中有哪些字母也就确定了,唯一的区别就是这些字母的前后顺序而已。
现在我们用一个由大写字母 A 和 B 构成的序列来描述这类字符串里各个字母的前后顺序:
l
如果字母 b 在字母 a 的后面,那么序列的第一个字母就是 A (After),否则序列的第一个字母就是 B (Before);
l
如果字母 c 在字母 b 的后面,那么序列的第二个字母就是 A ,否则就是 B;
l
如果字母 d 在字母 c 的后面,那么 …… 不用多说了吧?直到这个字符串的结束。
这规则甚是简单,不过有个问题就是同一个 AB 序列,可能有多个字符串都与之相符,比方说序列"ABA",就有"acdb"、"cadb"等等好几种可能性。说的专业一点,这一个序列实际上对应了一个字符串集合。那么现在问题来了:给你一个这样的AB 序列,问你究竟有多少个不同的字符串能够与之相符?或者说这个序列对应的字符串集合有多大?注意,只要求个数,不要求枚举所有的字符串。
注:如果结果大于10亿就返回-1。
我的最终解答(没有考虑溢出的情况):
// CODE 1
// the best way
// O(N^2)
int countABbest(const string& AB)
{
assert(AB.find_first_not_of("AB") == string::npos);
vector<int> current, next; // should we reserve these vectors?
current.push_back(1);
for (string::const_iterator letter = AB.begin();
letter != AB.end(); ++letter) {
next.resize(current.size()+1); // or next.insert(next.end(), 2, 0);
next[0] = 0; // in fact, we could set the entire vector to zero
if (*letter == 'A') {
partial_sum(current.begin(), current.end(), next.begin()+1);
} else {
partial_sum(current.rbegin(), current.rend(), next.begin()+1);
reverse(next.begin(), next.end());
}
swap(current, next);
}
return accumulate(current.begin(), current.end(), 0);
}
int main()
{
const char* AB = "ABBAAB";
printf("'%s' : %d/n", AB, countABbest(AB));
}
下面谈一谈我在解决这个问题时的思路。
第一步 初步分析
以下“字符串”特指题目中提到的由小写字母a、b、c等等组成的字符串,每个字母出现且仅出现一次。显然题目要求我们写一个函数f,f的输入是一个长度为v 的AB序列w,w代表了一个字符串集合s(集合中的元素都是长度为m(m=v+1)的字符串),f的返回值是这个集合的元素个数|s|,即|s|=f(w)。用高中学过的一点排列组合知识,可分析知:
1.
长度为m的字符串有m! 个(’!’ 表示阶乘)因为这相当于m个不同字母的全排列;
2.
长度为v的AB序列有2^v个(’^’ 表示指数)因为每个位置有2种可能,一共有v个位置;
3.
由于2^v <= m! (m=v+1),所以AB序列的数目不大于字符串的数目。
4.