關(guān)于統(tǒng)計(jì)數(shù)字問(wèn)題的算法
一本書(shū)的頁(yè)碼從自然數(shù)1開(kāi)始順序編碼直到自然數(shù)n。書(shū)的頁(yè)碼按照通常的習(xí)慣編排,每個(gè)頁(yè)碼都不含多余的前導(dǎo)數(shù)字0。例如第6頁(yè)用6表示而不是06或006。數(shù)字統(tǒng)計(jì)問(wèn)題要求對(duì)給定書(shū)的總頁(yè)碼,計(jì)算出書(shū)的全部頁(yè)碼中分別用到多少次數(shù)字0,1,2,3,.....9。
這個(gè)題目有個(gè)最容易想到的n*log10(n)的算法。這是自己寫(xiě)的復(fù)雜度為O(n*log10(n))的代碼:
void statNumber(int n) { int i, t; int count[10] = {0}; for(i = 1; i <= n; i++) { t = i; while(t) { count[t%10]++; t/=10; } } for(i = 0; i < 10; i++) { printf("%d/n", count[i]); } }
仔細(xì)考慮m個(gè)n位十進(jìn)制數(shù)的特點(diǎn),在一個(gè)n位十進(jìn)制數(shù)的由低到高的第i個(gè)數(shù)位上,總是連續(xù)出現(xiàn)10^i個(gè)0,然后是10^i個(gè)1……一直到10^i個(gè)9,9之后又是連續(xù)的10^i個(gè)0,這樣循環(huán)出現(xiàn)。找到這個(gè)規(guī)律,就可以在常數(shù)時(shí)間內(nèi)算出第i個(gè)數(shù)位上每個(gè)數(shù)字出現(xiàn)的次數(shù)。而在第i個(gè)數(shù)位上,最前面的10^i個(gè)0是前導(dǎo)0,應(yīng)該把它們減掉。
這樣,可以只分析給定的輸入整數(shù)n的每個(gè)數(shù)位,從面可以得到一個(gè)log10(n)的算法,代碼如下:
void statNumber(int n) { int m, i, j, k, t, x, len = log10(n); char d[16]; int pow10[12] = {1}, count[10] = {0}; for(i = 1; i < 12; i++) { pow10[i] = pow10[i-1] * 10; } sprintf(d, "%d", n); m = n+1; for(i = 0; i <= len; i++) { x = d[i] - '0'; t = (m-1) / pow10[len-i]; count[x] += m - t * pow10[len-i]; t /= 10; j = 0; while(j <= x-1) { count[j] += (t + 1) * pow10[len-i]; j++; } while(j < 10) { count[j] += t * pow10[len - i]; j++; } count[0] -= pow10[len-i]; /* 第i個(gè)數(shù)位上前10^i個(gè)0是無(wú)意義的 */ } for(j = 0; j < 10; j++) { printf("%d/n", count[j]); } }
通過(guò)對(duì)隨機(jī)生成的測(cè)試數(shù)據(jù)的比較,可以驗(yàn)證第二段代碼是正確的。
對(duì)兩段代碼做效率測(cè)試,第一次隨機(jī)產(chǎn)生20萬(wàn)個(gè)整數(shù),結(jié)果在我的電腦上,第二段代碼執(zhí)行1.744秒。第一段代碼等我吃完鈑回來(lái)看還是沒(méi)反應(yīng),就強(qiáng)行關(guān)了它。
第二次產(chǎn)生了1000個(gè)整數(shù),再次測(cè)試,結(jié)果第一段代碼在我的電腦上執(zhí)行的時(shí)間是
10.1440秒,而第二段代碼的執(zhí)行時(shí)間是0.0800秒。
其原因是第一段代碼時(shí)間復(fù)雜度為O(n*log10(n)),對(duì)m個(gè)輸入整數(shù)進(jìn)行計(jì)算,則需要的時(shí)間為 1*log10(1) + 2*log10(2) + ... + m*log10(m), 當(dāng)n > 10時(shí),有
n*log10(n) > n,所以上式的下界為11+12+....+m,其漸近界為m*m。對(duì)于20萬(wàn)個(gè)測(cè)試數(shù)據(jù),其運(yùn)行時(shí)間的下界就是4*10^10。
同樣可得第二段代碼對(duì)于n個(gè)輸入數(shù)據(jù)的運(yùn)行時(shí)間界是n*log10(n)的。
上面的代碼中有個(gè)pow10數(shù)組用來(lái)記錄10^i,但10^10左右就已經(jīng)超過(guò)了2^32,但是題目給定的輸入整數(shù)的范圍在10^9以內(nèi),所以沒(méi)有影響。
原著中給出的分析如下:
考察由0,1,2...9組成的所有n位數(shù)。從n個(gè)0到n個(gè)9共有10^n個(gè)n位數(shù)。在這10^n個(gè)n位數(shù)中,0,1,2.....9第個(gè)數(shù)字使用次數(shù)相同,設(shè)為f(n)。f(n)滿足如下遞推式:
n>1:
f(n) = 10f(n-1)+10^(n-1)
n = 1:
f(n) =1
由此可知,f(n) = n*10^(n-1)。
據(jù)此,可從高位向低位進(jìn)行統(tǒng)計(jì),再減去多余的0的個(gè)數(shù)即可。
著者的思想說(shuō)的更清楚些應(yīng)該是這樣:
對(duì)于一個(gè)m位整數(shù),我們可以把0到n之間的n+1個(gè)整數(shù)從小到大這樣來(lái)排列:
000......0
.............
199......9
200......0
299......9
.........
這樣一直排到自然數(shù)n。對(duì)于從0到199......9這個(gè)區(qū)間來(lái)說(shuō),拋去最高位的數(shù)字不看,其低m-1位恰好
就是m-1個(gè)0到m-1個(gè)9共10^(m-1)個(gè)數(shù)。利用原著中的遞推公式,在這個(gè)區(qū)間里,每個(gè)數(shù)字出現(xiàn)的次數(shù)
(不包括最高位數(shù)字)為(m-1)*10^(m-2)。假設(shè)n的最高位數(shù)字是x,那么在n之間上述所說(shuō)的區(qū)間共有
x個(gè)。那么每個(gè)數(shù)字出現(xiàn)的次數(shù)x倍就可以統(tǒng)計(jì)完這些區(qū)間。再看最高位數(shù)字的情況,顯然0到x-1這些
數(shù)字在最高位上再現(xiàn)的次數(shù)為10^(m-1),因?yàn)橐粋€(gè)區(qū)間長(zhǎng)度為10^(m-1)。而x在最高位上出現(xiàn)次數(shù)就是
n%10^(m-1)+1了。接下來(lái)對(duì)n%10^(m-1),即n去掉最高位后的那個(gè)數(shù)字再繼續(xù)重復(fù)上面的方法。直到
個(gè)位,就可以完成題目要求了。
比如,對(duì)于一個(gè)數(shù)字34567,我們可以這樣來(lái)計(jì)算從1到34567之間所有數(shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù):
從0到9999,這個(gè)區(qū)間的每個(gè)數(shù)字的出現(xiàn)次數(shù)可以使用原著中給出的遞推公式,即每個(gè)數(shù)字出現(xiàn)4000次。
從10000到19999,中間除去萬(wàn)位的1不算,又是一個(gè)從0000到9999的排列,這樣的話,從0到34567之間
的這樣的區(qū)間共有3個(gè)。所以從00000到29999之間除萬(wàn)位外每個(gè)數(shù)字出現(xiàn)次數(shù)為3*4000次。然后再統(tǒng)計(jì)
萬(wàn)位數(shù)字,每個(gè)區(qū)間長(zhǎng)度為10000,所以0,1,2在萬(wàn)位上各出現(xiàn)10000次。而3則出現(xiàn)4567+1=4568次。
之后,拋掉萬(wàn)位數(shù)字,對(duì)于4567,再使用上面的方法計(jì)算,一直計(jì)算到個(gè)位即可。
下面是自己的實(shí)現(xiàn)代碼:
void statNumber_iterative(int n) { int len, i, k, h, m; int count[10] = {0}; int pow10[12] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; char d[16]; len = log10(n); /* len表示當(dāng)前數(shù)字的位權(quán) */ m = len; sprintf(d, "%d", n); k = 0; /* k記錄當(dāng)前最高位數(shù)字在d數(shù)組中的下標(biāo) */ h = d[k] - '0'; /* h表示當(dāng)前最高位的數(shù)字 */ n %= pow10[len]; /* 去掉n的最高位 */ while(len > 0) { if(h == 0) { count[0] += n + 1; h = d[++k] - '0'; --len; n %= pow10[len]; continue; } for(i = 0; i < 10; i++) { count[i] += h * len * pow10[len-1]; } for(i = 0; i < h; i++) { count[i] += pow10[len]; } count[h] += n + 1; --len; h = d[++k] - '0'; n %= pow10[len]; } for(i = 0; i <= h; i++) { count[i] += 1; } /* 減去前導(dǎo)0的個(gè)數(shù) */ for(i = 0; i <= m; i++) { count[0] -= pow10[i]; } for(i = 0; i < 10; i++) { printf("%d/n", count[i]); } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
上一篇:一些語(yǔ)言的按行讀取文件的代碼實(shí)現(xiàn)小結(jié)
欄 目:C語(yǔ)言
下一篇:深入分析C語(yǔ)言分解質(zhì)因數(shù)的實(shí)現(xiàn)方法
本文標(biāo)題:關(guān)于統(tǒng)計(jì)數(shù)字問(wèn)題的算法
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2917.html
您可能感興趣的文章
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)- 解析最少換車(chē)次數(shù)的問(wèn)題詳解
- 01-10c語(yǔ)言 跳臺(tái)階問(wèn)題的解決方法
- 01-10用貪心法求解背包問(wèn)題的解決方法
- 01-10深入第K大數(shù)問(wèn)題以及算法概要的詳解
- 01-10深入N皇后問(wèn)題的兩個(gè)最高效算法的詳解
- 01-10深入解析Linux下\r\n的問(wèn)題
- 01-10數(shù)組中求第K大數(shù)的實(shí)現(xiàn)方法
- 01-10深入理解大數(shù)與高精度數(shù)的處理問(wèn)題
- 01-10節(jié)序問(wèn)題:解析大小的端判定
- 01-10基于c的for循環(huán)中改變變量值的問(wèn)題


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改