剑指43-整数n中数字1出现的个数

本文详细介绍了如何解决输入整数n后,计算1到n所有数字中1出现次数的问题。通过分析数字规律,提出两种数学规律:1) 数位上的1出现次数与该位数值的关系;2) 当位数上数字为1时,对1的贡献。并给出了相应的时间复杂度为O(log10(n))的代码实现,展示了如何利用这些规律进行高效计算。
摘要由CSDN通过智能技术生成


题目描述

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。


一道看上去很简单的题目,我的暴力解法毫无意外的超时了,没法子,只能看题解了,好家伙,看了半个小时才弄懂。

数学规律题目真心是最难的:没看过肯定写不来,看过了也不一定记得住规律。。。

一、找规律

规律1:

在1~10中,他们的个位数上出现1的次数为1;
在11~100中,他们十位数出现1的个数为10;
在101~1000中,他们的百位数出现1的个数为100;
。。。
在100***1【共n位】~100***00【共n + 1】,他们的第n位上出现1的个数为10^(n-1)


假设所求整数为23014:
从1~23010中,共包含2301个10,从23011~23014各位数上含1的有1个,故按照规律1,个位数上含义的有 2301 + 1 = 2302;

从1 ~ 23000中,含有230个100, 从23001 ~ 23014中,含有一个十位数的1的个数有4个【即个位数的数目】,故十位数上1的个数为10 * 230 + 4 = 231【之所以含有,是因为十位数大于等于1】

从1 ~ 23000中,含有23个1000,从23001 ~ 23014中,百位数不含1,故百位数上含一的个数为: 100 * 23 = 2300

从1 ~ 20000中,含有2个10000,从20001 ~ 23014中,千位数上含一的个数有1000个,故
千位数含一的个数有 2 * 1000 + 1000 = 30000

万位数含一的从10000~19999共10000个,

综上,每个数位上的和即为23014中含有1的数字总和。

规律2:
对于数字n:

从右向左数的第i位数字上含有1的个数为:
假设数字n的各数位从左到右排列为n_z, n_y. …n_i, n_(i-1)…,n_2,n_1,设n_i = x,
n_z,n_y…n_(i + 1)组成的数字为high,n_(i - 1)…n_2, n_1组成的数字为low:

  • 若x == 0,则第i位不必参与第i位的1的计数,i位所含1的个数只与高位有关:
    如上面的n_3 == 0,则只需要将高位*100即可,可推出一般规律:
    x == 0, 第i位上1的总数目为high * 10^(i - 1)
  • 若x == 1,则第i位的1的计数不仅与高位有关,还与低位的数字有关,低位出现了多少次1xxx,就有多少个1【还要加上xxx全为0的情况】
    不失一般性,x==1时,第i位上1的个数为high * 10^(i - 1) + low + 1
  • 若x > 1,则低位所含1的个数是确定的,可以理解为这个数位上可以完整的取一次,
    如上面的千位为3,则从1000 ~ 1999这1000个数都能取一次,加上前面所含的2个10000,共有 (2 + 1) * 1000 = 3000个千位1.
    规律:若x > 1,则i位所含1的个数为 (high + 1) * 10^(i - 1)

二、代码

设当前为cur,cur的前面几个数字组成high,后几个数字组成low,当前数位对应的基数设为digit【如百位设置为100】
初始化 : cur = n % 10, high = n / 10, low = 0,digit = 1

迭代:

  • cur = high % 10; //下个cur为high的最低位
  • high = high / 10; //下个high为high的前m - 1位
  • low = low + cur * digit //low为当前数位后面的余数
  • digit = digit * 10

循环跳出条件:
high==0 && cur == 0 ,表示指针已经移到数位的左外。


复杂度分析:
时复:循环内部都是常数阶,循环外部与digit有关,即O(log10(n))
空复:几个变量,原地工作。


 public int countDigitOne(int n) {
        int digit = 1, high = n / 10, cur = n % 10, low = 0;
        int count = 0;
        while (high != 0 || cur != 0) {
            if (cur == 0) count += high * digit;
            else if (cur == 1) count += high * digit + low + 1;
            else count += (high + 1) * digit;
            low += cur * digit;
            cur = high % 10;
            high /= 10;
            digit *= 10;
            
        }

        return count;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值