在王曉東編著的《演算法設計與實驗題解》中看到的這個問題,問題描述如下:
一本書的頁碼從自然數1開始順序編碼直到自然數n。書的頁碼按照通常的習慣編排,每個頁碼都不含多餘的前導數字0。例如第6頁用6表示而不是06或006。數字統計問題要求對給定書的總頁碼,計算出書的全部頁碼中分別用到多少次數字0,1,2,3,.....9。
例如:輸入11,輸出:1,4,1,1,1,1,1,1,1,1
在部落格中看到博主進行了很好地分析!!!
現黏貼部分如下:
這個題目有個最容易想到的n*log10(n)的演算法。這是自己寫的複雜度為o(n*log10(n))的**:
void statnumber(int n) ;
for(i = 1; i <= n; i++)
}for(i = 0; i < 10; i++) }
仔細考慮m個n位十進位制數的特點,在乙個n位十進位制數的由低到高的第i個數字上,總是連續出現10^i個0,然後是10^i個1……一直到10^i個9,9之後又是連續的10^i個0,這樣迴圈出現。找到這個規律,就可以在常數時間內算出第i個數字上每個數字出現的次數。而在第i個數字上,最前面的10^i個0是前導0,應該把它們減掉。
這樣,可以只分析給定的輸入整數n的每個數字,從面可以得到乙個log10(n)的演算法,**如下:
void statnumber(int n) , count[10] = ;
for(i = 1; i < 12; i++)
sprintf(d, "%d", n);
m = n+1;
for(i = 0; i <= len; i++)
while(j < 10)
count[0] -= pow10[len-i]; /* 第i個數字上前10^i個0是無意義的 */
}for(j = 0; j < 10; j++)
} 通過對隨機生成的測試資料的比較,可以驗證第二段**是正確的。
對兩段**做效率測試,第一次隨機產生20萬個整數,結果在我的電腦上,第二段**執行1.744秒。第一段**等我吃完鈑回來看還是沒反應,就強行關了它。
第二次產生了1000個整數,再次測試,結果第一段**在我的電腦上執行的時間是
10.1440秒,而第二段**的執行時間是0.0800秒。
其原因是第一段**時間複雜度為o(n*log10(n)),對m個輸入整數進行計算,則需要的時間為 1*log10(1) + 2*log10(2) + ... + m*log10(m), 當n > 10時,有
n*log10(n) > n,所以上式的下界為11+12+....+m,其漸近界為m*m。對於20萬個測試資料,其執行時間的下界就是4*10^10。
同樣可得第二段**對於n個輸入資料的執行時間界是n*log10(n)的。
上面的**中有個pow10陣列用來記錄10^i,但10^10左右就已經超過了2^32,但是題目給定的輸入整數的範圍在10^9以內,所以沒有影響。
原著中給出的分析如下:
考察由0,1,2...9組成的所有n位數。從n個0到n個9共有10^n個n位數。在這10^n個n位數中,0,1,2.....9第個數字使用次數相同,設為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)。
據此,可從高位向低位進行統計,再減去多餘的0的個數即可。
著者的思想說的更清楚些應該是這樣:
對於乙個m位整數,我們可以把0到n之間的n+1個整數從小到大這樣來排列:
000......0
.............
199......9
200......0
299......9
.........
這樣一直排到自然數n。對於從0到199......9這個區間來說,拋去最高位的數字不看,其低m-1位恰好
就是m-1個0到m-1個9共10^(m-1)個數。利用原著中的遞推公式,在這個區間裡,每個數字出現的次數
(不包括最高位數字)為(m-1)*10^(m-2)。假設n的最高位數字是x,那麼在n之間上述所說的區間共有
x個。那麼每個數字出現的次數x倍就可以統計完這些區間。再看最高位數字的情況,顯然0到x-1這些
數字在最高位上再現的次數為10^(m-1),因為乙個區間長度為10^(m-1)。而x在最高位上出現次數就是
n%10^(m-1)+1了。接下來對n%10^(m-1),即n去掉最高位後的那個數字再繼續重複上面的方法。直到
個位,就可以完成題目要求了。
比如,對於乙個數字34567,我們可以這樣來計算從1到34567之間所有數字中每個數字出現的次數:
從0到9999,這個區間的每個數字的出現次數可以使用原著中給出的遞推公式,即每個數字出現4000次。
從10000到19999,中間除去萬位的1不算,又是乙個從0000到9999的排列,這樣的話,從0到34567之間
的這樣的區間共有3個。所以從00000到29999之間除萬位外每個數字出現次數為3*4000次。然後再統計
萬位數字,每個區間長度為10000,所以0,1,2在萬位上各出現10000次。而3則出現4567+1=4568次。
之後,拋掉萬位數字,對於4567,再使用上面的方法計算,一直計算到個位即可。
下面是自己的實現**:
void statnumber_iterative(int n) ;
int pow10[12] = ;
char d[16];
len = log10(n); /* len表示當前數字的位權 */
m = len;
sprintf(d, "%d", n);
k = 0; /* k記錄當前最高位數字在d陣列中的下標 */
h = d[k] - '0'; /* h表示當前最高位的數字 */
n %= pow10[len]; /* 去掉n的最高位 */
while(len > 0)
for(i = 0; i < 10; i++)
for(i = 0; i < h; i++)
count[h] += n + 1;
--len;
h = d[++k] - '0';
n %= pow10[len];
}for(i = 0; i <= h; i++)
/* 減去前導0的個數 */
for(i = 0; i <= m; i++)
for(i = 0; i < 10; i++) }
然後,根據他的思路,我自己也寫了乙個程式:
#include #include int a[10]=,pow10[10]; //陣列a存放各位數字的出現次數,pow10陣列存放10的階
void fun(int n)
sprintf(d,"%d",n); //數n存放到d陣列中
headnum=d[0]-'0'; //取首個數字
for (i=0;i<10;i++) //首先對除去最高位數後的剩餘數進行計數
remainer=n%(int)pow(10,deep-1);
index=0;
while (index但是這個程式是錯誤的!!
修改一下:
#include #include #include int a[10],pow10[10]; //陣列a存放各位數字的出現次數,pow10陣列存放10的階
void fun(int n)
memset(a,0,sizeof(a));
sprintf(d,"%d",n); //數n存放到d陣列中
headnum=d[0]-'0'; //取首個數字
remainer=n%(int)pow(10,deep-1);
index=0;
while (index
統計數字問題
問題描述如下 一本書的頁碼從自然數1開始順序編碼直到自然數n。書的頁碼按照通常的習慣編排,每個頁碼都不含多餘的前導數字0。例如第6頁用6表示而不是06或006。數字統計問題要求對給定書的總頁碼,計算出書的全部頁碼中分別用到多少次數字0,1,2,3,9。演算法設計與分析習題 分析 考察由0,1,2.9...
統計數字問題
問題描述 給定乙個整數n,統計從1到n 數字最高位不允許為0 這麼多個數中0,1,2,3,4,5,6,7,8,9分別出現的次數。問題解決 採用遞迴求解統計每乙個數字0,1,2,9出現的次數累加。源 include stdafx.h include int results 10 void count ...
統計數字問題
目錄 問題描述 演算法設計 資料輸入 結果輸出 課後答案 有人將其稱為 補0遞迴法 舉例說明 666 完整 執行結果 一本書的頁碼從自然數1開始順序編碼直到自然數n。書的頁碼按照通常的習慣編排,每個頁碼都不含多於的前導數字0.例如,第6頁用數字6表示,而不是06或006等。數字計數問題要求對給定書的...