1. 程式人生 > >編程之美-1的個數

編程之美-1的個數

pac 都差不多 正整數 輸入 增加 理解 效率 for fun

1的數目

題目:給定一個十進制正整數N,寫下從1開始,到N的所有整數,然後數一下其中出現所有“1”的個數。

例如:

N=2,寫下1~2。這樣只出現了1個“1”。

N=12,我們會寫下1,2,3,4,5,6,7,8,9,10,11,12,這樣,1的個數是5。

問題是:

1.寫一個函數F(N),返回1到N之間出現的“1”的個數,比如F(12)=5;

2.滿足條件“F(N)=N”的最大的N是多少?

我們就先來看看問題1的解法吧;

問題一,解法1:

 1 #include <iostream>
 2 using namespace std;
 3 int f(int n)
 4 {
 5     int sum=0;
 6     while(n!=0)
 7     {
 8         sum+=(n%10==1)?1:0;
 9         n/=10;
10     }
11     return sum;
12 }
13 int fun(int N)
14 {    
15     int count=0;
16     for(int i = 1;i <= N; ++i)
17     {
18         count+=f(i);
19     }
20     return count;
21 
22 }
23 int main()
24 {
25     const int N=2;
26     const int M=12;
27     cout << fun(N) <<endl;
28     cout << fun(M) <<endl;
29     return 0;
30 }

問題一,解法2:

#include <iostream>
using namespace std;
int fun(int N)
{	
	int count=0,k;
	for(int i = 1;i <= N; ++i)
	{  k=i;
       while(k!=0)
	   {
		   if(k%10==1) count++;
		   k/=10;
	   }

	}
	return count;
}
int main()
{
	const int N=2;
	const int M=12;
	cout << fun(N) <<endl;
	cout << fun(M) <<endl;
	return 0;
}

其實這2種解法的思路都差不多,就是寫法有點不同,解法一是書上的寫法,解法2是我我自己寫的,大家覺得那種好理解就理解那種吧,但是這個算法的致命的是代碼的效率和復雜度

但是我們有沒有考慮過如果N為1個億的數呢。如果我們采用上面的程序來計算的話大概需要40秒的時間才能計算出來,隨著數的不斷增加而計算效率就越來越低了。

那麽我們能不能找個更快的方法來解決這個問題呢?要提高效率我們不能用這種遍歷的方法,要采用另外一種思路,

下面我們來繼續分析下這個思路,我們直接來看三個重點

個位數1的個數 規律:

  1. 如果N的個位數大於等於1,那麽個位數出現1的個數是十位數+1;
  2. 如果N的個位數為0,那麽個位數出現1的個數為十位數的數字;

整數 1的個數 1的列舉
5 1 1
15 2 1,11
25 3 1,11,21
35 4 1,11,21,31

十位數1的個數 規律

  1. 如果N的十位數等於1,那麽十位數出現1的個數是個位數+1;
  2. 如果N的十位數大於1,那麽十位數出現1的個數為10;

整數 1的個數 1的列舉
25 10 10,11,12,13,14,15,16,17,18,19
35 10 10,11,12,13,14,15,16,17,18,19
95 10 10,11,12,13,14,15,16,17,18,19
125 20 10,11,12,13,14,15,16,17,18,19,110,111,112,...,118,119
725 80 10-19,110-119,210-219,310-319,410-419,510-519,610-619,710-719
3225 330 10-19,20-29,...510-519,610-619,...1010-1019,1110-1119,1210-1219,...,3110-3119,3210-3219

百位數1的個數 規律

如果百位上的數字為0,則可以知道,百位上可能出現1的次數由更高位決定,比如12 013,則可以知道百位出現1的情況可能是100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,一共有1 200個。也就是由更高位數字(12)決定,並且等於更高位數字(12)×當前位數(100)。

如果百位上的數字為1,則可以知道,百位上可能出現1的次數不僅受更高位影響,還受低位影響,也就是由更高位和低位共同決定。例如對於12 113,受更高位影響,百位出現1的情況是100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,一共1 200個,和上面第一種情況一樣,等於更高位數字(12)×當前位數(100)。但是它還受低位影響,百位出現1的情況是12 100~12 113,一共114個,等於低位數字(123)+1。

如果百位上數字大於1(即為2~9),則百位上可能出現1的次數也僅由更高位決定,比如12 213,則百位出現1的可能性為:100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,12 100~12 199,一共有1 300個,並且等於更高位數字+1(12+1)×當前位數(100)。

通過上面的分析和總結 我們可以寫出如下代碼:

 1 LONGLONG Sum1s(ULONGLONG n)
 2 
 3 {
 4 
 5     ULONGLONG iCount = 0;
 6 
 7 
 8     ULONGLONG iFactor = 1;
 9 
10 
11     ULONGLONG iLowerNum = 0;
12 
13     ULONGLONG iCurrNum = 0;
14 
15     ULONGLONG iHigherNum = 0;
16 
17 
18     while(n / iFactor != 0)
19 
20     {
21 
22         iLowerNum = n - (n / iFactor) * iFactor;
23 
24         iCurrNum = (n / iFactor) % 10;
25 
26         iHigherNum = n / (iFactor * 10);
27 
28 
29         switch(iCurrNum)
30 
31         {
32 
33         case 0:
34 
35             iCount += iHigherNum * iFactor;
36 
37             break;
38 
39         case 1:
40 
41             iCount += iHigherNum * iFactor + iLowerNum + 1;
42 
43             break;
44 
45         default:
46 
47             iCount += (iHigherNum + 1) * iFactor;
48 
49             break;
50 
51         }
52 
53 
54         iFactor *= 10;
55 
56     }
57 
58 
59     return iCount;
60 
61 
62 }

這個方法只要分析N就可以得到fN),避開了從1到N的遍歷,輸入長度為Len的數字N的時間復雜度為OLen),即為O(ln(n)/ln(10)+1)。在筆者的計算機上,計算N=100 000 000,相對於第一種方法的40秒時間,這種算法不到1毫秒就可以返回結果,速度至少提高了40 000倍。

PS:題目和思路來自於編程之美,編程之美書籍是一本不錯的書,裏面涉及了很多算法和面試知識等等,喜歡編程的朋友可以去買來看看 是個不錯的選擇, 好了今天就寫到這裏吧,在後面我還會一直分析些有趣的算法,喜歡的點個關註吧。

編程之美-1的個數