本届大赛由微软必应词典冠名,必应词典(Bing Dictionary)是微软推出的新一代英语学习引擎,里面收录了很多我们常见的单词。但现实生活中,我们也经常能看到一些毫无规则的字符串,导致词典无法正常收录,不过,我们是否可以从无规则的字符串中提取出正规的单词呢?
例如有一个字符串"iinbinbing",截取不同位置的字符‘b’、‘i’、‘n’、‘g’组合成单词"bing"。若从1开始计数的话,则‘b’ ‘i’ ‘n’ ‘g’这4个字母出现的位置分别为(4,5,6,10) (4,5,9,10),(4,8,9,10)和(7,8,9,10),故总共可以组合成4个单词”bing“。
咱们的问题是:现给定任意字符串,只包含小写‘b’ ‘i’ ‘n’ ‘g’这4种字母,请问一共能组合成多少个单词bing?
字符串长度不超过10000,由于结果可能比较大,请输出对10^9 + 7取余数之后的结果。
我的做法是:
(1)将b、i、n、g分别放入四个桶中(实际放的不是这些字符),每个桶有个记录桶内当前字符数量的变量num(初始值为0),桶内的每个元素记录的内容是这样的:
如果是b桶,那么里面的元素记录的是这个b字符后面的第一个i字符在桶i内的索引(桶的索引从0开始,也就是桶的num变量值);
如果是i桶,那么里面的元素记录的是这个i字符后面的第一个n字符在桶n内的索引;
g桶只需要记录桶内的字符数量即可;
桶内每插入一个元素,桶的num变量+1.
这可以通过一次从前向后扫描字符串得到,这部分的复杂度为O(n),以题目中的"iinbinbing"为例,桶的构建结果如下:
(2)根据(1)计算数量,以这个例子进行说明:
第3个n字符后面的g字符数量为1-0=1(桶g的num值减去这个n字符对应位置保存的桶g的索引值)
第2个n字符后面的g字符数量为1-0=1
第1个n字符后面的g字符数量为1-0=1
并用计算出来的这些值代替桶中原来的值,如下:
第4个i字符后面的n,g序列的数量为:for j=2 to 3-1 :sum(n[j]) = 1
第3个i字符后面的n,g序列的数量为:for j=1 to 3-1 :sum(n[j]) = 1+1 =2
第2个i字符后面的n,g序列的数量为:for j=0 to 3-1 :sum(n[j]) = 1+1+1 =3
第1个i字符后面的n,g序列的数量为:for j=0 to 3-1 :sum(n[j]) = 1+1+1 =3
并用计算出来的这些值代替桶中原来的值,如下:
第2个b字符后面的i,n,g序列的数量为:for j=3 to 4-1 :sum(i[j]) = 2+1 =3
第1个b字符后面的i,n,g序列的数量为:for j=2 to 4-1 :sum(i[j]) = 1
那么结果就是3+1=4。
到这里你应该明白了计算过程。
这个计算过程可以优化:每个循环可以利用上一个循环的计算结果加上本次循环需要增加的量来计算:
比如计算“第3个i字符后面的n,g序列的数量为”,可以通过第4个i字符后面的n,g序列的数量+增加的量来计算。
优化后计算过程的复杂度也为O(n)。
(3)综上复杂度为O(n)
(4)代码如下,计算部分未优化:
- #include <stdio.h>
- #include <iostream>
- #include <string>
- using namespace std;
- class Test {
- public:
- static int howmany (string s){
- int div=1000000007;
- long long res=0;
- int *tmp[4];//其实3个就可以了
- int nums[4];
- for(int i=0;i<=3;++i){
- tmp[i]=new int[s.size()+1]; //
- nums[i]=0;
- }
- for(int i=0;i<=s.size();++i){//可以使用memset,会更快点
- tmp[0][i]=0;
- tmp[1][i]=0;
- tmp[2][i]=0;
- tmp[3][i]=0;
- }
- for(int i=0;i<s.size();++i){
- if(s[i]=='b'){
- tmp[0][nums[0]]=nums[1];
- ++nums[0];
- }else if (s[i]=='i'){
- //可以优化
- tmp[1][nums[1]]=nums[2];
- ++nums[1];
- }else if(s[i]=='n'){
- tmp[2][nums[2]]=nums[3];
- ++nums[2];
- }else {
- ++nums[3];
- }
- }
- for(int i=nums[2]-1;i>=0;--i){
- tmp[2][i]=nums[3]-tmp[2][i];
- }
- for(int i=nums[1]-1;i>=0;--i){
- int j=tmp[1][i];
- tmp[1][i]=0;
- for(;j<nums[2];++j){
- //这里可以优化,可以减少加法的次数
- //通过利用tmp[1][i+1]的值
- tmp[1][i]+=tmp[2][j];
- }
- }
- long long t=0;
- for(int i=nums[0]-1;i>=0;--i){
- int j=tmp[0][i];
- for(;j<nums[1];++j){
- //这里可以优化,可以减少加法的次数
- //通过利用上一次i循环的t的值
- t+=tmp[1][j];
- t%=div;
- }
- }
- return t;
- }
- };
- //start 提示:自动阅卷起始唯一标识,请勿删除或增加。
- int main()
- {
- cout<<Test::howmany("iinbinbing")<<endl;
- }
- //end //提示:自动阅卷结束唯一标识,请勿删除或增加。
- public class Test
- {
- public static int howmany(String s){
- int bNum = 0;
- int biNum = 0;
- int binNum = 0;
- int bingNum = 0;
- for(int i=0;i<s.length();i++){
- switch (s.charAt(i)) {
- case 'b':bNum++;break;
- case 'i':biNum=biNum%1000000007+bNum; break;
- case 'n':binNum = binNum%1000000007+biNum; break;
- case 'g':bingNum = bingNum%1000000007+binNum; break;
- default:
- break;
- }
- }
- return bingNum%1000000007;
- }
- //start 提示:自动阅卷起始唯一标识,请勿删除或增加。
- public static void main(String args[])
- {
- System.out.println(howmany("binbinggg"));
- }
- //end //提示:自动阅卷结束唯一标识,请勿删除或增加。
- }