算法深度学习1

今天看到一道非常经典的题,题目很简单,求非负数1到N之间出现1的个数。

这题用暴力的话直接爆内存,肯定不行。然后我仔细分析了一种比较认可的思路,其巧妙程度让我叹为观止。

我们认真分析一下这题,1的出现位置不外乎个位,十位,百位,千位等等,所以我们可以采取的策略就是将所有的位置上的1出现的次数分别计算,然后再相加,最后得到的就是我们要的答案。

比如随便举几个例子:

N=5,那么个位上为1有哪些数?1 共1个(M=1)

N=63,那么个位上为1有哪些数?1 11 21 31 41 51 61 共7个(M=7)

N=167,那么个位上为1有哪些数?1  11  21 31 41 ... 161 共17个(M=17)

N=1321,那么各位上为1有哪些数字? 1 11 21 31 41 ... 1321 共133个(M=133)

这里似乎就有了一点规律,有没有发现个位上为1的数似乎与该数的十位有点关系,对,就是前面为的数字+1.(如1321,前面位为132)那么暂时可以得出M=N/10+1

然后我们来看看十位的情况

N=12,那么十位上位1的数有哪些?10 11 12 共3个(M=3)

N=23,那么十位上位1的数有哪些?10 11 12 ... 19共10个(M=10)

N=126,那么十位上位1的数有哪些?10 11 12 ... 118 119 共20个(M=20)

N=302,那么十位上位1的数有哪些?10 11 12 ... 218 219 共30个(M=30)

N=1315,那么十位上位1的数有哪些?10 11 12 ... 1314 1315 共136个(M=136)

假设当前十位上的数为n,可看出:

n=1   ==>   M=N/10+N%10+1;  (上面第1,5两种情况,N%10表示个位数)

n!=1   ==>   n>1,则每一个百位就会有一个10 11 12... 19,所以M=(N/100+1)*10;   (上面第2,3种情况)

                   n=0,则当前百位不会有10,11,12...19,而前面的百位都会有,所以M=N/100*10  ==>  (N/100+0)*10;

                   可以看出是否+1与n的大小有关,整合一下,那么M=(N/100+(n+8)/10)*10 ==> (N/100*10+n+8)/10*10;

然后我们继续整合,仔细观察可以发现,n=1时,M=N/10+n   ==>   (N/100+(n+8)/10)*10+N%10+1  == > (N/100*10+n+8)/10*10+N%10+1这样写是成立的!!!

N/100*10+N/10%10 ==> N/10

然后我们就把n用N来替换,n=N/10%10:

(N/100*10+N/10%10+n+8)/10*10 ==> (N/10+8)/10*10;

综上:M=(N/10+8)/10*10+(N/10%10==1)*(N%10+1);

到了这里,我们不妨大胆推测一下,每个位置上的数和它前面位,当前位,后面位有关。

例: N=1324  个位为1的总和为133  (这结果与前面132,当前位2,后面为4有关:132+1==133)

         N=115  十位上为1的总和为16  (这结果与前面11,当前位1,后面为5有关:10+1+5==16)

那么我们得出的个位公式就可以转换一下了: M=N/10+1 ==> (N+8)/10*1+(N%10==1)*1==>(N+8)/10*1+(N%10==1)*(N%1+1)

可以明显看出公式似乎和当前所处的位置有关。那么我们大胆的推测一下:

那么设前面为a(即N/i=a,其中i表示当前的位置,默认个位为1,i*=10,即十位为10,一次类推),后面为b(即N%i=b)可以暂时得出通用方法:

         M=(a+8)/10*i+(a%10==1)*(b+1)

经过验证,这种方法是正确的!在这里我从一个思考者的角度来分析,而不是从一个辨证者的角度。这里再附上源码一份

 int NumberOf1Between1AndN_Solution(int n)
    {
    int a,b,sum=0;
        for(int i=1;i<=n;i*=10){
            b=n%i;
            a=n/i;
            sum+=(a+8)/10*i+(a%10==1)*(b+1);
        }
        return sum;
    }

那我们这里推广一下,如果是1到N中j的出现次数(j表示0~9之间的任意正整数)

通用公式为:M=(a+9-j)/10*i+(a%10==j)*(b+1);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值