题目大意
计算小于或等于给定数字的正整数中数字 1 的个数。
解题思路
我们来观察几个规律。
先看数字个位中 1 出现次数的规律:
(0), 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29
…
容易观察到,每10个非负整数,个位中就会出现 1 个数字 1。
以此类推,每100个非负整数,十位中就会出现 10 个数字 1。
… 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, …
… 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, …
这些规律很容易理解,但是给定的数字不一定都满足整十整百整千的规律。一般的数字有两种情况,一种是截断了连续出现数字 1 的区间,另一种是不截断连续出现数字 1 的区间。
个位情况
个位每10个非负整数只出现 1 次,不存在所谓的截断问题。计算个位出现的数字 1 时只需要计算 n / 10,然后再判断 n 的个位是不是大于 0。
十位情况
与个位不同,十位上的数字 1 最长连续出现 10 次,因此需要考虑截断的问题。判断十位上出现的数字 1 时,除了要计算 10 * (n / 100) 以及判断 n 的后两位是不是大于等于 10 以外,还要考虑比 10 大多少。
具体说来,如果末两位是 15,那么在十位上则发生了截断的情况,十位出现的数字 1 还有 (15 - 10 + 1) 个;如果末两位是 23,则没有发生截断的情况,十位上出现的数字 1 还有 10 个。
用公式表达出来就是 ex = n % 100 - 10 + 1; if (ex > 10) ex = 10;
。
能够理清十位的情况,继续往上也就没有难度了。
完整代码
class Solution {
public:
int countDigitOne(int n) {
if (n < 0) return 0;
int cnt = 0;
long long base = 10;
int tmp;
while (n >= base) {
tmp = n / base;
cnt += (tmp * base / 10);
if (base * tmp + base / 10 <= n) {
tmp = n - base * tmp - base / 10;
tmp += 1;
if (tmp > base / 10) tmp = base / 10;
cnt += tmp;
}
base *= 10;
}
tmp = n / base;
cnt += (tmp * base / 10);
if (base * tmp + base / 10 <= n) {
tmp = n - base * tmp - base / 10;
tmp += 1;
if (tmp > base / 10) tmp = base / 10;
cnt += tmp;
}
return cnt;
}
};