《编程之美》读书笔记——[1~N]出现“1”的次数

 拿到题目,最开始就是想着先不管效率先解出来,想法跟书上的第一种解法一样。

然后开始想怎样提高效率,naive方法的问题在于是对1~N每个数逐个地考察,没有考虑前后数之间的关联,没有充分利用1~N这样连续区间的特点。

我最开始联想到学《算法导论》课的时候讲平摊分析时候那个“二进制数平摊代价”计算,也是从逐位地考虑,但是那个是考虑从“0”变成“1”的时候的代价,跟本题十进制中“1”的个数统计不太一样,例如12,13, ... ,19没有变位发生,但是这时候十位上的"1"还是要计算的。

然后后面就是举例子琢磨,但是跟书上不大一样。我是利用 分治的思想:
以N=154为例,定义函数f[a,b]为区间[a,b]内出现“1”的次数。
目标求f[1,154],显然f[1,154] = f[1,100] + f[101,154]。为什么要凑f[1,100]?因为整十整百可以快速计算。
f[1,100]可以看做是三个位填入数字
-----------------------------------
|   0~1    |  0~9     |  0~9    | 
-----------------------------------
不妨假设N=a*10^x,
如果最高位a=1,则后面只能全为“0”,所以最高位为“1”只有1次;
如果最高位a>1,则当最高位取“1”,后面各位能为“0~9”,所以最高位为“1”有10^x次。
对于非最高位,若该位取“1”,则其余非最高位可取"0~9",最高位取"0~(a-1)",故每个非最高位位出现“1”次数为a*10^(x-1),总共有x个非最高位。
因此,若N=a*10^x,则f[1,N] = (a=1?1:10^x) + x*(a*10^(x-1))。
上面是“治”,接下来就是“分”,接着以N=154和N=254举例,
f[1,154] = f[1,100] + f[101,154],这里f[101,154]的特点在于百位上的“1”一直都在,可以提取出来,有
f[101,154] = 54 + f[1,54],但是如果高位不是“1”则不需要提取,如f[201,254] = f[1,54]。
最终可以分解成f[1,154] = f[1,100] + 54 + f[1,50] + f[1,4],对于f[1,100]、f[1,50]和f[1,4]上面的“治”的式子都是适用的。
f[1,100] = 1 + 2*1*10 = 21; 【100,10,11,...,19,01,11,21,...,91】
f[1,50] = 10 + 1*5*1 = 15; 【10,11,...,19,01,11,21,31,41】
f[1,4] = 1 + 0 = 1。【1】
因此,合并起来等于f[1,154] = 21 + 54 + 15 + 1 = 91。

书上给出的解法也是通过观察得到 规律,其思路是固定看一位,确定该位为“1”,看更高位和更低位对其限制。
百位上如果是0,则由其更高位决定,例如12034,有100~199,1100~1109,2100~2199,...,11100~11199,共12*100个。
百位上如果是1,则由其更高位和更低位决定,例如12134,有100~199,1100~1109,2100~2199,...,11100~11199,再加上12100~12134,共12*100+35个,。
百位上如果大于1,则由其更高位决定,例如12234,有100~199,1100~1109,2100~2199,...,11100~11199,12100~12199,共13*100个。
对于其余各位,情况类似。

这种思路的好处在于形式上的整齐简洁,但是其实不容易归纳出来。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值