【題目】
求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。
【思路】
兩種方法!分別是暴力解決和使用數學技巧。
【代碼】
1. 暴力解決
從1到n遍歷,每次通過對10求餘數判斷整數的個位數字是不是1,大於10的除以10之後再循環判斷。我們對每個數字都要做除法和求餘運算以求出該數字中1出現的次數。如果輸入數字n,n有位,我們需要判斷每一位是不是1,那麼時間複雜度爲。
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n){
if (n <= 0) {
return 0;
}
int result = 0;
for (int i = 1; i <= n; i++) {
int tmp = i;
while (tmp != 0) {
if (tmp % 10 == 1) {
result++;
}
tmp /= 10;
}
}
return result;
}
};
2. 數學技巧
從網上可以看到大量用編程之美給出的規律解題的思路,看了挺多篇文章,這裏說說自己的理解。
這裏以 736 爲例進行分析:
1)個位
我們把736分成兩個部分,一部分是個位左邊的73,另一部分是個位右邊的(因爲沒有,這裏就不用考慮了)。個位可以從0~9變化10次,此記爲一輪,每一輪出現一次數字1。然後,會出現74輪(第一部分73從0變化到73)。因此可以得出個位出現1的次數爲74。
三種情況:
-
當個位大於1,即上面的情況。可以得出個位出現1的次數爲74。
-
當個位等於1,和上面的情況相同。可以得出個位出現1的次數爲74。
-
當個位等於0時,即730。第74輪個位到0就結束了,所以個位沒有出現1。因此可以得出個位出現1的次數爲73。
2)百位
我們繼續把736分成兩個部分,一部分是個位左邊的7,另一部分是個位右邊的6。百位可以從09變化10次,個位同樣是可以從09變化10次,個位變化一輪百位變化一次,百位變化一輪更高位才變化一次。另外百位變化一輪,1會出現10次,這是因爲在這一輪中百位爲1時,個位可以從0~9變化10次。 然後,百位會出現8輪(第一部分7從0變化到7)。因此可以得出百位出現1的次數爲8*10=80次。
三種情況:
- 當百位大於1時,即上面的情況。可以得出百位出現1的次數爲80。
- 當百位等於1時,即716。那麼第8輪中百位出現1的次數和個位有關,即出現了7次(個位從0~6變化7次)。因此可以得出百位出現1的次數爲7*10+7=77。
- 當百位等於0時,即706。那麼第8輪中百位到0就結束了,不會出現1。因此可以得出百位出現1的次數爲7*10=70。
3)更高位
更高位的處理情況和百位的情況類似。
下面貼上AC代碼的兩種寫法,時間複雜度都是。
寫法一:
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n){
if (n <= 0) {
return 0;
}
int result = 0, tmp = n, base = 1;
while (tmp > 0) {
int a = tmp % 10; // 本位的數
tmp = tmp / 10; // 高位(本位左邊的數)
result += tmp*base; // a==0的情況
if (a == 1) {
result += n%base + 1;
}
else if (a > 1) {
result += base;
}
base *= 10;
}
return result;
}
};
寫法二(簡潔優美):
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n){
// 統計次數
int result = 0;
for (int i = 1; i <= n; i *= 10) {
// 計算高位和低位
//之所以補8,是因爲當百位爲0,則a/10==(a+8)/10,
//當百位>=2,補8會產生進位位,效果等同於(a/10+1)
int a = n / i, b = n % i;
result += (a + 8) / 10 * i + ((a % 10 == 1) ? (b + 1) : 0);
}
return result;
}
};