【题目】
求出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;
}
};