1. 題目描述
輸入一個整數n,求從1到n這n個整數的十進制表示中1出現的次數。例如輸入12,從1到12這些整數中包含1的數字有1,10,11和12,1一共出現了5次。
2. 題目來源
第一次看到是在《劍指Offer》第2版上,面試題32。leetcode和牛客網上都有這道題。
3. 本文的目的
看了《劍指Offer》上的解法,我覺得不能算好:
- 這段解釋描述有些不清晰,而且沒有圖,難以理解。
- 從書中給出的實現上來看,顯得有些凌亂。
在這篇博客裏,會給出一個我對這道題的解法,包括完整的解題思路,完整代碼,時間複雜度分析,以及在leetcode和牛客網上的提交結果。
4. 解題思路
考慮將n的十進制的每一位單獨拿出討論,每一位的值記爲weight。
1) 個位
從1到n,每增加1,weight就會加1,當weight加到9時,再加1又會回到0重新開始。那麼weight從0-9的這種週期會出現多少次呢?這取決於n的高位是多少,看圖:
以534爲例,在從1增長到n的過程中,534的個位從0-9變化了53次,記爲round。每一輪變化中,1在個位出現一次,所以一共出現了53次。
再來看weight的值。weight爲4,大於0,說明第54輪變化是從0-4,1又出現了1次。我們記1出現的次數爲count,所以:
如果此時weight爲0(n=530),說明第54輪到0就停止了,那麼:
2) 十位
對於10位來說,其0-9週期的出現次數與個位的統計方式是相同的,見圖:
不同點在於:從1到n,每增加10,十位的weight纔會增加1,所以,一輪0-9週期內,1會出現10次。即rount*10。
再來看weight的值。當此時weight爲3,大於1,說明第6輪出現了10次1,則:
如果此時weight的值等於0(n=504),說明第6輪到0就停止了,所以:
如果此時weight的值等於1(n=514),那麼第6輪中1出現了多少次呢?很明顯,這與個位數的值有關,個位數爲k,第6輪中1就出現了k+1次(0-k)。我們記個位數爲former,則:
3) 更高位
更高位的計算方式其實與十位是一致的,不再闡述。
4) 總結
將n的各個位分爲兩類:個位與其它位。
對個位來說:
- 若個位大於0,1出現的次數爲
round*1+1
- 若個位等於0,1出現的次數爲
round*1
對其它位來說,記每一位的權值爲base,位值爲weight,該位之前的數是former,舉例如圖:
則:
- 若weight爲0,則1出現次數爲
round*base
- 若weight爲1,則1出現次數爲
round*base+former+1
- 若weight大於1,則1出現次數爲
rount*base+base
比如:
- 534 = (個位1出現次數)+(十位1出現次數)+(百位1出現次數)=(53*1+1)+(5*10+10)+(0*100+100)= 214
- 530 = (53*1)+(5*10+10)+(0*100+100) = 213
- 504 = (50*1+1)+(5*10)+(0*100+100) = 201
- 514 = (51*1+1)+(5*10+4+1)+(0*100+100) = 207
- 10 = (1*1)+(0*10+0+1) = 2
public class Solution {
/*
public int NumberOf1Between1AndN_Solution(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;
}
*/
/*
將n的各個位分爲兩類:個位與其它位。
對個位來說:
若個位大於0,1出現的次數爲round*1+1
若個位等於0,1出現的次數爲round*1
對其它位來說,記每一位的權值爲base,位值爲weight,該位右邊的數是former,該位左邊的數是round,
則:
若weight爲0,則1出現次數爲round*base
若weight爲1,則1出現次數爲round*base+former+1
若weight大於1,則1出現次數爲rount*base+base
*/
public int NumberOf1Between1AndN_Solution(int n) {
if(n <=0)
return 0;
int count = 0;
int round = n;
int base = 1;
while(round > 0){
int weight = round % 10;
round /= 10;
count += round * base;
if(weight == 1)
count += n % base + 1;
else if(weight > 1){
count += base;
}
base *= 10;
}
return count;
}
}
6. 時間複雜度分析
由分析思路或者代碼都可以看出,while循環的次數就是n的位數,logn(以10爲底),而循環體內執行的操作都是有限次的,所以時間複雜度爲O(logn)。