《劍指offer》面試題32----從1到n整數中1出現的次數


題目:輸入一個整數n,求從1到n這n個整數的十進制表示中1出現的次數。例如輸入12,從1到12這些整數中包含1的數字有1,10,11和12,1一共出現了5次。

解法一:不考慮時間效率的解法(略)

ps:我感覺是個程序員都能想到這第一種解法,時間復雜度O(nlogn)。這個方法沒有什么意義,但是簡單易懂,去小公司足夠了,這里不講了。

解法二:分析數字規律,時間復雜度O(logn).

這是我寫這篇文章的初衷。《劍指offer》洋洋灑灑寫了幾十行代碼,然而在leetcode上大神卻只用了5行!當天晚上智障,腦子全是漿糊,竟然沒有看懂什么意思=。=,我一度懷疑智商受到了碾壓。然而在今天睡眠比較充足,頭腦比較清醒的情況下終於理順了思路~

其實這道題目很多地方都有講,包括《編程之美》,但是也有20行左右的代碼,沒耐心了。其它的一些帖子講的亂七八糟,這對於我這種愛簡潔,愛干凈,還有嚴重強迫症的人是不能忍的,下面強迫症患者要開始裝逼了。。。

先上代碼:

 1 package test;
 2 
 3 public class Question_32 {
 4     public static int countDigitOne(int n) {
 5         int ones = 0;
 6         for (long m = 1; m <= n; m *= 10)
 7             ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
 8         return ones;
 9     }
10 
11     public static void main(String[] args) {
12         // TODO Auto-generated method stub
13         System.out.println(countDigitOne(12));
14 
15     }
16 
17 }

核心代碼只有line4~line9。leetcode原鏈接: https://discuss.leetcode.com/topic/18054/4-lines-o-log-n-c-java-python 牛客網鏈接: https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6 。

裝逼模式開啟:

我們從一個5位的數字講起,先考慮其百位為1的情況。分3種情況討論:

百位數字>=2  example: 31256  當其百位為>=時,有以下這些情況滿足(為方便起見,計312為a,56為b):

    100 ~   199

  1100 ~  1199

        .....

 31100 ~ 31199

 余下的都不滿足!

因此,百位>=2的5位數字,其百位為1的情況有(a/10+1)*100個數字   (a/10+1)=>對應於 0 ~ 31,且每一個數字,對應范圍是100個數(末尾0-99)

百位數字 ==1 example: 31156 當其百位為1時,有以下這些情況滿足:

     100 ~   199

   1100 ~  1199

          ......

  30100 ~ 30199

  31100 ~ 31156

因此,百位為1的5位數字,共有(a/10)*100+(b+1)

百位數字 ==0 example: 31056 當其百位為0時,有以下這些情況滿足:

     100 ~   199

   1100 ~  1199

 30100 ~ 30199

  其余都不滿足

因此,百位數為0的5位數字,共有(a/10)*100個數字滿足要求

我們可以進一步統一以下表達方式,即當百位>=2或=0時,有[(a+8)/10]*100,當百位=1時,有[(a+8)/10]*100+(b+1)。用代碼表示就是: [(a+8)/10]*100+(a%10==1)?(b+1):0;

為什么要加8呢?因為只有大於2的時候才會產生進位等價於(a/10+1),當等於0和1時就等價於(a/10)。另外,等於1時要單獨加上(b+1),這里我們用a對10取余是否等於1的方式判斷該百位是否為1。

 

Question:有缺陷或邏輯錯誤嗎?

有人可能會有疑惑,比如11100,這個數在考慮百位為1的時候算作了一次,在考慮千位的時候也算了一次,在考慮萬位為1的時候又算了一次,一共計了3次,這不是明顯重復嗎?

我的回答是,不重復!

分析:題目中要我們統計出現的1的個數,那么我們可以看到11100一共是3個1,如果剔除了重復的情況只考慮一次才會是問題。換言之,在計算從1到n整數中1的出現次數時,我們把10位出現1的情況個數加上百位出現1的情況個數一直加到最高位是1的情況的個數,這里面一個數可能被統計過多次;11100百位出現1,千位和萬位都為1,那么被重復統計了3次

 

代碼分析:

 

1 public static int countDigitOne(int n) {
2         int ones = 0;
3         for (long m = 1; m <= n; m *= 10)
4             ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
5         return ones;
6     }

 

for (long m = 1; m <= n; m *= 10) 在這里的作用是,從個位開始考慮,再到十位,百位,千位,一直到超出這個數!為什么m要用long型呢?因為n可能沒有超過整型的表達范圍(int剛好可以表示n),而10*m恰恰有可能剛剛超過!ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0); 這里ones用於表示1的個數,當m=100時,n/m其實代表的是a,而n%m代表的是b,此時考慮的是百位為1的情況;當m=1000,自然考慮的就是千位等於1的情況了~ 至於為什么加8,那個三目運算符是干嘛子用的上面都已經講過了。

最后,總結一下。這道題網上答案太多了,但是我覺得只有這種方法最讓人眼前一亮。抖機靈的不少,比如用java字符串處理的,自以為很厲害,其實根本沒含金量(時間復雜度O(nlogn)啊!)關鍵是這還有贊同的,不知道算法分析是怎么學的。《劍指offer》和《編程之美》的答案可能曾經是最佳,但是現在被更好的方法替換了,而作者並不知情。一本好書看3遍,勝過3本好書看一遍!相信一個月后,我對這道題的印象可能就沒有多少了,及時整理,利人利己,溫故而知新~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM