計算 1 到 N 之間 有多少個 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。出現了5個“1”。

問題求解:

解法一:

       最直接的方法就是從1開始遍歷到N,將其中每一個數中含有“1”的個數加起來,就得到了問題的解。

      代碼如下:

複製代碼
1 publiclong CountOne3(long n) 2 { 3 long i =0,j =1; 4 long count =0; 5 for (i =0; i <= n; i++) 6 { 7 j = i; 8 while (j !=0) 9 { 10 if (j %10==1) 11 count++; 12 j = j /10; 13 } 14 } 15 return count; 16 }
複製代碼

        此方法簡單,容易理解,但它的問題是效率,時間複雜度爲O(N * lgN),N比較大的時候,需要耗費很長的時間。

 

解法二:

         我們重新分析下這個問題,對於任意一個個位數n,只要n>=1,它就包含一個“1”;n<1,即n=0時,則包含的“1”的個數爲0。於是我們考慮用分治的思想將任意一個n位數不斷縮小規模分解成許多個個位數,這樣求解就很方便。

        但是,我們該如何降低規模?仔細分析,我們會發現,任意一個n位數中“1”的個位可以分解爲兩個n-1位數中“1”的個數的和加上一個與最高位數相關的常數C。例如,f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位爲1的數字個數,這裏就是10,11,12;f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位爲1的數字的個數,這裏就是100~132;f(232) = 2*f(100 - 1) + f(32) + 100,因爲232大於199,所以它包括了所有最高位爲1的數字即100~199,共100個。

        綜上,我們分析得出,最後加的常數C只跟最高位n1是否爲1有關,當最高位爲1時,常數C爲原數字N去掉最高位後剩下的數字+1,當最高位爲1時,常數C爲10bit,其中bit爲N的位數-1,如N=12時,bit=1,N=232時,bit=2。

       於是,我們可以列出遞歸方程如下:

       if(n1 == 1)

           f(n) = f(10bit-1) + f(n - 10bit)  + n - 10bit+ 1;

       else

           f(n) = n1*f(10bit-1) + f(n – n1*10bit) + 10bit;

       遞歸的出口條件爲:

       if(1<n<10)  return 1;

       else if (n == 0) return 0;

       基於此,編寫如下代碼:

   

複製代碼
1 publiclong CountOne(long n) 2 { 3 long count =0; 4 if (n ==0) 5 count =0; 6 elseif (n >1&& n <10) 7 count =1; 8 else 9 { 10 long highest = n;//表示最高位的數字 11  int bit =0; 12 while (highest >=10) 13 { 14 highest = highest /10; 15 bit++; 16 } 17 18 int weight = (int)Math.Pow(10, bit);//代表最高位的權重,即最高位一個1代表的大小 19  if (highest ==1) 20 { 21 count = CountOne(weight -1) 22 + CountOne(n - weight) 23 + n - weight +1; 24 } 25 else 26 { 27 count = highest * CountOne(weight -1) 28 + CountOne(n - highest * weight) 29 + weight; 30 } 31 } 32 return count; 33 }
複製代碼

         此算法的優點是不用遍歷1~N就可以得到f(N)。經過我測試,此算法的運算速度比解法一快了許多許多,數字在1010內時,算法都可以在毫秒級內結束,而解法一在計算109時,時間超過了5分鐘。但此算法有一個顯著的缺點就是當數字超過1010時會導致堆棧溢出,無法計算。

        還有就是,我嘗試了許久也沒有計算出此算法的時間複雜度到底是多少,似乎是O(lg2N),我並不確定,希望知道的高手能給予解答。

 

解法三:

         解法二告訴我們1~ N中“1”的個數跟最高位有關,那我們換個角度思考,給定一個N,我們分析1~N中的數在每一位上出現1的次數的和,看看每一位上“1”出現的個數的和由什麼決定。

       1位數的情況:

       在解法二中已經分析過,大於等於1的時候,有1個,小於1就沒有。

       2位數的情況:

       N=13,個位數出現的1的次數爲2,分別爲1和11,十位數出現1的次數爲4,分別爲10,11,12,13,所以f(N) = 2+4。

       N=23,個位數出現的1的次數爲3,分別爲1,11,21,十位數出現1的次數爲10,分別爲10~19,f(N)=3+10。

       由此我們發現,個位數出現1的次數不僅和個位數有關,和十位數也有關,如果個位數大於等於1,則個位數出現1的次數爲十位數的數字加1;如果個位數爲0,個位數出現1的次數等於十位數數字。而十位數上出現1的次數也不僅和十位數相關,也和個位數相關:如果十位數字等於1,則十位數上出現1的次數爲個位數的數字加1,假如十位數大於1,則十位數上出現1的次數爲10。

       3位數的情況:

       N=123

       個位出現1的個數爲13:1,11,21,…,91,101,111,121

       十位出現1的個數爲20:10~19,110~119

       百位出現1的個數爲24:100~123

       我們可以繼續分析4位數,5位數,推導出下面一般情況: 

       假設N,我們要計算百位上出現1的次數,將由三部分決定:百位上的數字,百位以上的數字,百位一下的數字。

       如果百位上的數字爲0,則百位上出現1的次數僅由更高位決定,比如12013,百位出現1的情況爲100~199,1100~1199,2100~2199,…,11100~11199,共1200個。等於更高位數字乘以當前位數,即12 * 100。

       如果百位上的數字大於1,則百位上出現1的次數僅由更高位決定,比如12213,百位出現1的情況爲100~199,1100~1199,2100~2199,…,11100~11199,12100~12199共1300個。等於更高位數字加1乘以當前位數,即(12 + 1)*100。

        如果百位上的數字爲1,則百位上出現1的次數不僅受更高位影響,還受低位影響。例如12113,受高位影響出現1的情況:100~199,1100~1199,2100~2199,…,11100~11199,共1200個,但它還受低位影響,出現1的情況是12100~12113,共114個,等於低位數字113+1。

       綜合以上分析,寫出如下代碼:

 

複製代碼
1 publiclong CountOne2(long n) 2 { 3 long count =0; 4 long i =1; 5 long current =0,after =0,before =0; 6 while((n / i) !=0) 7 { 8 current = (n / i) %10; 9 before = n / (i *10); 10 after = n - (n / i) * i; 11 12 if (current >1) 13 count = count + (before +1) * i; 14 elseif (current ==0) 15 count = count + before * i; 16 elseif(current ==1) 17 count = count + before * i + after +1; 18 19 i = i *10; 20 } 21 return count; 22 23 }
複製代碼

 

     此算法的時間複雜度僅爲O(lgN),且沒有遞歸保存現場的消耗和堆棧溢出的問題。
發佈了87 篇原創文章 · 獲贊 2 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章