現在我們用一個由大寫字母 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次,即便剪枝,也不能減少多少循環次數, 而用這個公式,寫個遞歸方法,可以很快的算出答案。
但仍然有個問題: