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

TypantK發表於2019-03-11

原貼地址:https://www.cnblogs.com/xuanxufeng/p/6854105.html


 

 

題目描述

求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。

 

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

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

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

先上程式碼:

package test;

public class Question_32 {
    public static int countDigitOne(int n) {
        int ones = 0;
        for (long m = 1; m <= n; m *= 10)
            ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
        return ones;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(countDigitOne(12));

    }

}

 

裝逼模式開啟:


我們從一個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次

轉載者的BB:計算的是每一位上出現1的個數,不是出現1的數的個數

 

程式碼分析:

 

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

 

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本好書看一遍!相信一個月後,我對這道題的印象可能就沒有多少了,及時整理,利人利己,溫故而知新~

相關文章