剑指 Offer 43.1~n 整数中1出现的次数
首先明确一点,我们的算法是给定 n,我们将 1~n 的所有数的个位出现 1 的次数 + 1~n 的所有数的十位出现 1 的次数 + 1~n 的所有数的百位出现 1 的次数 + …,就是最终的答案。我们通过找规律的方法,只需要知道 n,就能算出 1~n 的每一位上一共能出现多少个 1。请看下面的步骤:
我们首先来思考 n 的某一位可能是哪些数字,因为这会影响到我们找规律的公式。不难想到,n 的某一位要么是 0,要么是 1,要么是 2/3/4/…/9。
我们现在以十位为例子来推导我们的公式。我们设立一个变量 digit,用来表示当前的数位(digit 等于几当前的数位就是几位数)。所以如果是十位,digit = 10。
1、假设 n = 1204。此时十位是 0,我们把十位左边的叫做高位 high,所以这里 high = 12;把十位右边的叫做低位 low,所以这里 low = 4。十位能出现 1 的数字范围是:0010~1119,12xx没法使十位为1,高位的范围为00~11,低位的范围为0 ~ 9,因此十位出现1的次数为1210 = high * digit。
2、假设 n = 1214。此时十位是 1,同样我们有 high = 12,low = 4。十位能出现 1 的数字范围是:0010~1214,高位的范围为00~12,低位的范围为0 ~ 9(当高位为12时,地位只能为0 ~ 4),因此十位能出现1的次数为12 * 10 + 5 = high * digit + low + 1。
3、假设 n = 1234。此时十位是 3,同样我们有 high = 12,low = 4。十位能出现 1 的数字范围是:0010~1219,高位的范围为00 ~ 12,低位的范围为 0 ~ 9,因此十位能出现1的次数为13 * 10 = (high + 1) digit。
现在我们知道了如果找到 n 的某一位一共能出现 1 的次数。那我们如何循环地把每一位都累加起来呢?也就是代码逻辑怎么写呢?请看下面的步骤:
我们的思路是从个位开始计算起,所以我们首先让 digit = 1(表示个位);high = n / 10(表示个位左边的全部);low = 0(表示个位右边的全部,也就是没有);cur = n % 10(表示个位上的数)。
然后循环来移动 cur,确保 n 的每一位都被计算过。每次循环都用 cur 当前的值来匹配上述的三个公式,从而计算该位 1 一共能出现的次数。
注意我们循环退出的条件是 high 和 cur 都等于 0。因为都等于 0 就意味着 n 已经彻底遍历完了。不然的话:
假如退出的条件只有 high 等于 0:那假如 n = 1024,然后此时 cur 在千位,则 cur = 1,high = 0,low = 024。如果此时退出循环,我们还没来得及计算千位能出现的 1 的次数。
假如退出的条件只有 cur 等于 0:那假如 n = 1024,然后此时 cur 在百位,则 cur = 0,high = 1,low = 24。如果此时退出循环,我们还没来得及计算百位和千位能出现的 1 的次数。
class Solution {
public int countDigitOne(int n) {
int digital = 1, cur = n % 10, high = n / 10, low = 0;
int sum = 0;
while(high != 0 || cur != 0) {
if(cur == 0) sum += high * digital;
else if(cur == 1) sum += high * digital + low + 1;
else sum += (high + 1) * digital;
low += cur * digital;
cur = high % 10;
high = high / 10;
digital *= 10;
}
return sum;
}
}
时间复杂度 O(logn):循环次数为n的位数log10(n)。
空间复杂度 O(1)