现在我们用一个由大写字母 A 和 B 构成的序列来描述这类字符串里各个字母的前后顺序:
如果字母 b 在字母 a 的后面,那么序列的第一个字母就是 A (After),否则序列的第一个字母就是 B (Before);
如果字母 c 在字母 b 的后面,那么序列的第二个字母就是 A ,否则就是 B;
如果字母 d 在字母 c 的后面,那么 …… 不用多说了吧?直到这个字符串的结束。
这规则甚是简单,不过有个问题就是同一个 AB 序列,可能有多个字符串都与之相符,比方说序列“ABA”,就有“acdb”、“cadb”等等好几种可能性。说的专业一点,这一个序列实际上对应了一个字符串集合。那么现在问题来了:给你一个这样的 AB 序列,问你究竟有多少个不同的字符串能够与之相符?或者说这个序列对应的字符串集合有多大?注意,只要求个数,不要求枚举所有的字符串。
|
最后总结一下吧: 实际上这个问题的算法直接做是 O(n^3) 的,做些优化之后可以到 O(n^2)。这里的关键是只考虑可能性而不要去想具体如何插入:请在脑子里面建一张三角形的表,每一行对应于 AB 字符串中的一个字符,每一行中第 i 个元素代表了到目前为止最后一个字母放在第 i 个位置的可能性个数。比方说:
代码
这里第一行 A ,当前最后一个字母是 b ,b 放在第 0 个位置的可能性个数是 0 ,放在第 1 个位置的可能性个数是 1 ;第二行 B ,当前最后一个字母是 c ,放在第 0、1 两个位置的可能性个数都是 1 ,放在最后一个位置的可能性是 0 …… 依此类推。很容易可以看出,如果当前行的次序是 A ,那么放在第 i 个位置的可能性就是上一行从 0 到 i-1 位置的可能性个数之和,否则就是从 i 到最后一个位置的可能性个数之和。逐步向下做,直到用完输入的 AB 字符串即可,最后的总数就是对上述表格最后一行求个和。这里每一行中各个列的可能性是没有重叠的,因为最后一个字母的位置各不相同。 复杂度么,这个表格一共有 O(n^2) 项,每一格构造最多花 O(n) ,所以总复杂度自然是 O(n^3) ,稍做优化可以把计算每一格的开销降到 O(1) ,复杂度可以降到 O(n^2) 。这里给个参考实现 —— C++ 写的,不熟悉的朋友只能说见谅了,好在还算简单,没用什么特殊的东西:
代码
其中 vector 是 C++ 标准库的成员,实际上就是变长数组。我用 prev_line 和 current_line 分别记录上述三角形表格的前一行和当前行,最后对最后一行求一个总和。 |
为什么f(13)=6, 因为1,2,3,4,5,6,7,8,9,10,11,12,13.数数1的个数,正好是6.
我把c的代码贴出来!
他计算到4000000000,用的是剪枝。
- #include "stdafx.h"
- #include <windows.h>
- #include <stdlib.h>
- int f(int n);
- int count1(int n);
- int cal(unsigned int number,int nwei,int count1,unsigned int ncount);
- int gTable[10];
- const unsigned int gMAX = 4000000000L;
- int main(int argc, char* argv[])
- {
- int i;
- unsigned int n=1;
- unsigned int ncount = 0;
- int nwei = 0;
- int ncount1;
- /*if(argc>1)
- {
- n = atoi(argv[1]);
- ncount = f(n);
- printf("f(%d) = %d/n",n,ncount);
- }*/
- int beginTime=GetTickCount();
- //init gTable
- for(i=0;i<10;++i)
- {
- n *= 10;
- gTable[i] = f(n-1);
- }
- n=0;
- nwei = 0;
- ncount1 = 0;
- while(n<gMAX)
- {
- unsigned int temp;
- temp = 1;
- ncount =cal(n,nwei,ncount1,ncount);
- for(i=0;i<nwei;++i)
- temp *= 10;
- n += temp;
- if( (n/temp)/10 == 1)
- ++nwei;
- ncount1 = count1(n);
- }
- int endTime=GetTickCount();
- endTime-=beginTime;
- printf("time: %d ms/n",endTime);
- return 0;
- }
- int f(int n)
- {
- int ret = 0;
- int ntemp=n;
- int ntemp2=1;
- int i=1;
- while(ntemp)
- {
- ret += (((ntemp-1)/10)+1) * i;
- if( (ntemp%10) == 1 )
- {
- ret -= i;
- ret += ntemp2;
- }
- ntemp = ntemp/10;
- i*=10;
- ntemp2 = n%i+1;
- }
- return ret;
- }
- int count1(int n)
- {
- int count = 0;
- while(n)
- {
- if( (n%10) == 1)
- ++count;
- n /= 10;
- }
- return count;
- }
- int cal(unsigned int number,int nwei,int count1,unsigned int ncount)
- {
- int i,n=1;
- unsigned int maxcount;
- if(nwei==0)
- {
- ncount += count1;
- if(number == ncount)
- {
- printf("f(%d) = %d /n",number,number);
- }
- return ncount;
- }
- for(i=0;i<nwei;++i)
- n *= 10;
- maxcount = ncount + gTable[nwei-1];
- maxcount += count1*n;
- if(ncount > (number + (n-1)) )
- {
- return maxcount;
- }
- if(maxcount < number)
- {
- return maxcount;
- }
- n /= 10;
- for(i=0;i<10;++i)
- {
- if(i==1)
- ncount = cal(number+i*n,nwei-1,count1+1,ncount);
- else
- ncount = cal(number+i*n,nwei-1,count1,ncount);
- }
- return ncount;
- }
-
我们将数x表示成 head*10(n)+tail;
如:x=1234 , 则表示为 1* 10(3) + 234;
f(1234) = 1 * f(99) + 235 + f(234);
不用我解释吧
234 = 2 * 10(2) + 34;
f(234) = 2 * f(99) + 100 + f(34);
从1到234,有两次 f(99) ,从 100 到 199 共有100个1, 从 200到234 的 1的总和 = f(34)可以得出:
对于数x,
如果x<10
f(x) = x>=1 ? 1 : 0;
如果head = 1
f(x) = head * f(10(n) - 1) + f(tail) + tail+1
如果head > 1
f(x) = head * f(10(n) - 1) + f(tail) + 10(n);根据这个公式我们可以迅速的求出f(x),而如果用getCountOfOne(long number),只能得到某数中1的个数,对于从1到n的值,必须使用循环相加的方法,如果计算,123456789的值的话,就需要循环 123456789次,即便剪枝,也不能减少多少循环次数, 而用这个公式,写个递归方法,可以很快的算出答案。
但仍然有个问题:
本文探讨了两种有趣的计数问题:一是通过AB序列确定字符串排列的数量,介绍了算法实现并分析了其时间复杂度;二是计算从0到n范围内数字1出现的总次数,并给出了一种高效的解决方案。
8996

被折叠的 条评论
为什么被折叠?



