[Leetcode] 233. Number of Digit One

Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.

Example:

Input: 13
Output: 6 
Explanation: Digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.

这一题是cc150原题的简化版,算是里面比较有意思的一题。原题是Number of Digit X。不过做法没有区别。

这一题的思路在于一个digit一个digit的往下算。就是譬如在12345 这个数字里,个位数出现了多少次1,十位数出现了多少次1,百位数出现了多少次1,千位数出现了多少次1,万位数出现了多少次1。

在这个例子里。
1. 我们先看个位数会出现多少次1,因为12345里面,每10个数就必然会在个位数出现一个1在x0 ~ x9,所以首先是12340 / 10 = 1234个,然后因为5 大于 1,所以最后答案是1234 + 1 = 1235个1。(这里x指代任意一个数字)
2. 十位数上,因为12345里面,每100个数,就会出现10个十位数上的1在x10 ~ x19(举例,100 ~ 200里就有110 ~ 119, 200 ~ 300里就有210 ~ 219。如此类推)。 所以答案是12345 / 100 * 10 = 1230. 因为4 > 1,所以还会出现12310 ~ 12319 的区间的10个1。所以最后是1230 + 10 = 1240个1。如果这个是12315的话,那么因为1 == 1, 所以统计起来就只要看12310~12315的6个1,结果会变成1230 + 6 = 1236个1。
3. 同理,百位数上,每1000个数,会出现100个百位数上的1在x100 ~ x199(1000 ~ 2000有1100 ~ 1199.....之类的),所以12345里面,百位数上的1有12345 / 1000 * 100 = 1200。 又因为3 > 1,所以存在12100 ~ 12199这个区间。最后答案便是1200 + 100 = 1300。同理的,如果这里是12145,那么12100~12199这个区间里只有12100~12145 共46个数,如果是12045的话,12100~12199区间就一个都没有了。
4. 千位数上,每一万个数就有1000个1在区间x1000~x1999这里,所以12345里面,千位数上的1有12345 / 10000 * 1000 = 1000个,同理,因为2 > 1,所以还包含了11000 ~ 11999这1000个所以最后是2000个。
5. 万位数上,你可以同样理解为每十万个数就有10000个1在区间x10000 ~ x19999里面,12345 / 100000 = 0。所以大区间上这里是0,但是因为在10000~19999里面包含了10000~12345,所以万位数上有2346个。

最后总数便是1235 + 1240 + 1300 + 2000 + 2346 = 8121个

通过上面的流程,我们不难发现每一位上的计算都包含了两个部分。
第一个部分我们可以称之为大区间,譬如说,算个位数的时候,我们要看有多少个10,算十位数的时候有多少个100。每个大区间都有当前位数的1,算个位数每个10就有1个1,算十位数每个100就有10个1。

第二个部分我们可以称之为小区间,大区间算的是当前要算的位数之上的一位的倍数,小区间要算的就是剩下的部分。例如,在12345算百位数的时候,12000的部分就是大区间,345的部分就是小区间。

看得出来大区间的计算是很简单的,就是算上一位的倍数乘以当前的位数就可以了,譬如12345算百位数的时候,就取12345除以1000再乘以100。就是1200。

小区间的计算就需要分情况了,譬如在12345里面,小区间是345,因为百位数上3大于1,所以100~199的区间是被完全包含的。如果小区间是045的话,那么0 < 1,则100~199的区间是完全没有的。如果是145的话,那么100~199的区间就会被部分包含,100~145。

根据以上理论,得到代码如下:

    public int countDigitOne(int n) {
        int power10 = 1, result = 0, temp = n;
        while (temp > 0) {
            int curDigit= temp % 10;
            temp /= 10;
            int upperRange = temp * power10;
            result += upperRange;
            if (curDigit > 1) {
                result += power10;
            } else if (curDigit == 1) {
                result += n % power10 + 1;
            }
            power10 *= 10;
        }
        
        return result;
    }

大家是不是发现画风和我上面说的略微有些不同。你打开leetcode提示里面,那里只有一条:Beware of overflow. 其实,说真的,有时候,我是很讨厌leetcode刁难人的方式。因为按照我们上面的说法,当你遇到整型数譬如说1000000000的时候,正常套路是会在最后遇到10000000000去做模除的,然后就overflow了。真是日了狗了。这里的做法就是避开那种高一位的模除情况的。
还是用12345做例子,curDigit表示的是当前位数的数字

在个位数的时候,curDigit是5,upperRange也就是大区间还是1234,我们先直接加上大区间,然后因为curDigit是5,是大于1的,所以就直接加1.
在十位数的时候,curDigit已经是5了,upperRange的大区间变成了1230(其实原本大区间的计算是12345 / 100 * 10。而我们这里大区间是12345先除以10,再除以10,最后乘回10的做法,所以是等价的。其实防止overflow的做法就是不停的除以10去代替取除以一个比当前位数高一位的做法。所以你不会遇到overflow的情况,即使是在整型数的边界。),然后curDigit是4,我们通过n % power10拿到的其实就是小区间中除去最高位的后面的数字。譬如12345在取十位数的时候,小区间其实是45,但因为我们在上面解释的时候,我们说的是我们先看小区间的most significant digit,其实也就是我们得到的curDigit,然后如果大于1,则取当前位数,也就是百位数取100,十位数取10这样。如果等于1,就取除去most significant digit之外的数字再加1,譬如12145中如果算到百位数,我们就取45 + 1,如果是12315算到十位数,我们就取5 + 1。而这个取法,和n % power10 + 1也是等价的。所以最后为了避免overflow, 我们就换了一种数学意义上是一样但更取巧的做法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值