题目
对给定的n,统计1~n中每一位上1出现的次数,最后输出总和。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例:
输入:12
输出:5
思路
我们可以从低到高,以每一位为单位,去找到该位上1出现次数的规律,来统计次数。
以210为例:
首先是个位,210个位是0,个位出现1的个数是(被掩埋掉的1):高位的数据量,总计 21 个。
没有显式的1,但是该数字具有高位(百位,十位),这些都是通过个位进位来的,即高位每增加1,那么个位会循环往复 从 0 到 1 再到 9 最后到 0 进位,那么高位有多少个数,个位就出现过多少个 1(被掩埋掉了),这里高位是 21 所以个位出现过 21 个 1;
再进到十位,十位上是1,具有显式的1,所以十位上出现1的个数是(被掩埋掉的1 + 显式出现的1): 高位的数据量×10 + 低位的个数 + 1,总计 21 个。
因为十位是 100 才会向高位进1,因此十位的高位每增加1,十位上的1就会出现十次,这里十位的高位是2,因此十位上被掩盖的1就出现了2*10=20次,而十位上具有显式的1,则它出现次数和个位有关,个位是0,则十位显式出现 一次 1,如果个位是5,则十位显式出现六次 1;
再接着是百位,百位上是2,则百位上出现1的个数是(被掩盖的1):(高位的数据量+1)× 100,总计 100 个。
首先百位进位要1000个数,其中1的个数是100个,百位上的数比1大,表示百位上的1已经全部被掩盖,但是还没有达到进位的程度,因此 高位已有的数据量还要加上1,再乘以百位的权重 100 ,
因此 210 总计出现 142 个1。
从中推出一般规律:
我们从低位开始向上遍历:
用 cur 代表遍历到的第 i 位上的数字,
用 low 代表低于该位总计有多少数字,比如 210 中遍历到十位,则 low = 0;
用 high 代表高于该位总计有多少数字,比如 210 中遍历到十位,则 high = 2;
用 digit 代表该位的权重,digit = 10 ^ i,十位的权重自然是 10;
cur只有三种情况:
cur = 0,代表刚刚完成进位,该位贡献的 1 只有隐式进位的 1,其个数等于 high × digit;
cur = 1,该位贡献的1有隐式进位的 1 和显式的 1(记得加上低位全是0的情况) ,其个数等于 high × digit + low + 1;
cur = 2~9,该位贡献的1全部都是隐式进位的1(记得加上没有达成进位但是被掩埋的情况),其个数等于 ( high + 1 ) × digit ;
因此在遍历时维护好 cur 、 low 、high 和 digit 即可。
代码
public int countDigitOne(int n) {
// 初值
int digit = 1, res = 0;
// cur从个位开始,逐个对high取余,初值从n中取得
int high = n / 10, cur = n % 10, low = 0;
// 当还没有取到结束
while(high != 0 || cur != 0) {
// 三种情况分别讨论
if(cur == 0) res += high * digit;
else if(cur == 1) res += high * digit + low + 1;
else res += (high + 1) * digit;
// 更新
// low是低于cur的所有数字的数量统计,那么就是cur*digit的累加和
low += cur * digit;
// cur要进位,所以要对high取余,获取更高一级的位上的数字
cur = high % 10;
// high进位,持续自除
high /= 10;
// 进制递增
digit *= 10;
}
return res;
}