剑指 Offer 43. 1~n整数中1出现的次数 【LeetCode 233】

题目描述:

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

输入:   13
输出:    6 
解释:    数字 1 出现在以下数字中: 1, 10, 11, 12, 13 。

分析:

首先发现n的范围非常大,就是是用O(n)的时间复杂度,也是会超时的,所以这个题是个数学题或者说规律题,那种暴力的方法想都不要想了。

直接暴力法否定之后,那这是不是个规律题呢?于是用暴力法打了一部分数据的表格,企图去发现那个规律,打完表如下:

从表格中发现,左边的一列n在递增,但是答案有的时候随着递增,有的时候不变。比如94-99这一段,答案都是20,而111-118这一段,则n增加1,答案增加2。

显然,这也是看不出什么规律的,但是却也给了我们一起启示:为什么答案是这样变化的?

94-99变化,因为不涉及1,所以对答案没有贡献;但是111-118这一段,因为百位和是十位都是1,所以个位变化一次,相当于给答案增加2个1啊!

知道了变化的原因,那么我们看看答案是怎么算出来。

比如n=137,答案是72。 那么这72个1分别从哪里来呢?
    首先分析个位,个位的变化很明显就是1 2 3 4 5 6 7 8 9 0 1 . . . ,这样10个数字的一轮回中,是有1个1出现的。对于137来说,个位数的类似这样的轮回,有多少次呢? 因为137是13个10+7,所以很明显这样的轮回是13+1=14 次(为什么+1? 因为个位的7大于1,所以这个7也贡献了一次),个位对于的答案贡献1*14=14.
    再来分析十位,十位的变化和个位一样也是从1..9 0 1如此,但是十位出现一个1,对于答案来说贡献度是10,因为这个十位的1,后面的个位会循环10次,都会计入答案(例如10 11 12 ....19)。那么十位的1的轮回有几次呢? 前面百位数是1,所以是1+1=2次(加一的愿意是3本身大于1,也会贡献1次),十位答案贡献10*2=20。
    最后分析百位,百位的1因为前边没有更高位带动,所以只需要考虑后面多余出来的37,【100,101,102,137】很明显百位贡献的答案就是37+1=38。
    最终的答案就是个十百位加起来。14+20+38=72.

上面冗余繁琐的分析,总结就是:分别看每一位的贡献。   假如一个数 【1..n】K【1..low】,对于每一位数字,分3种情况判断:
   情况一:如果这个数的第K位数是大于1的,那么说明K位之前的1..n的数据的变化,都会引起第K位对答案的贡献。并且贡献是:(K-1)^10*(n+1)。n+1的原因是n会带动n个轮回,由于k本位大于1,也会带动一个轮回。
  情况二:如果K位数字==1,那么说明K之前的n位的n个轮回还是有的,是(K-1)^10*n,但是K本位是不足以带动一整个轮回的,所以不能加一,他只能带动1...low的次数,所以最终答案还要加上low+1,也就是(K-1)^10*n+low+1。
  情况三:如果K位数字==0,那么说明K之前的n个轮回还在,但是+1是不行了,因为本位不够1啊,+low+1也不存在,所以答案就直接是(k-1)^10*n.

代码:

class Solution {
public:
    int countDigitOne(int n) {
        int ans=0; //答案
        long long p=1;  //累乘器
        int low=0;  //另外一部分
        while (n!=0){
            int t=n%10;  //末位
            n=n/10;
            if (t>1) {
            	ans+=p*(n+1);
			}else
			if (t==1){
				ans+=p*n+low+1;
			}else
				ans+=p*n; 
            low=p*t+low;
            p=p*10;
        }
        return ans;
    }
};

代码中:分析中的第K位数字用t表示,n是高位,low是低位,用一个累乘变量p表示阶数的增加。

每次先分解出n的末位数字t,然后根据t和1的大小关系,分三种情况计算,同时维护low和p即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值