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