数一数1到n出现1的数目

来源:互联网 发布:谷歌学术 知乎 编辑:程序博客网 时间:2024/04/30 00:21

今天有点闷,和我的博客说说话吧,这个问题在脑子里萦绕有些久, 总坚信自己有24k智商,如此简单的问题,分分钟拿下,可是每次想不出来都遗憾作罢,原来也不过我的智商2.4k而已.^_^.

描述

在从1到n的数字当中1出现的数目.

分析

最先想到的方法应该是从1遍历到n看看这些数字总共有多少个1,不错的方法,你能首先想到这种方法说明不是一个钻牛角尖的人.不错. 不过n如果很大的话,这复杂度肯定是难以想像的.
这其中的确存在规律.
假设有n=”abcd”四位数, 现在分类讨论每一位为1的数总共有多少,
以b为例,分 (0~a-1)0(00~cd) (a)1(00~cd) (0~a)1(00~99) 三种

1.如果 b为0 b左边有 a种取法(注意从0开始取), b右边有cd +1 种取法.总共1的个数是 a*(cd+1);
2.如果 b为1 ,又分两种情况:

1)如果 b左边取 0 ~a-1, 右边10^2种 总共 a*(10^2)种
2)如果 b左边取 a, 右边有 cd+1种 总共 cd + 1种
该种情况下总共 a*(10^2) + cd +1 个数

3.如果 b>1, 左边有a+1种取法,右边有10^2种取法, 总共(a+1)*(10^2)种取法.

ok现在我们只需要遍历每一位,将每位的结果加起来就是结果. 但是现在还有一步要考虑,那就是边界条件. b是中间的一位, 那如果讨论a或者讨论d上述规则是否成立呢.

先看最高位, 首先a>=1,若a=1, b左边有1种取法,就是2 的2)情况,计算结果是正确的.
再看最低位, 如果d =0 , 有abc*(0+1)种,如果d=1, abc*(10^0) + (0+1),也符合预期
这样说来,最高位左边趣 0~0, 最低位右边区 0~0, 就可以统一起来

代码

所以程序处理的关键是,在遍历每一位,得到该位左边数字多大,右边数字多大,有几位.就可以了,因此就有了下面优化前代码:

int NumberOf1Between1AndN_Solution(int n)    {       int temp = n;       int sum  = 0;         int base = 1;       while(n)       {          int b = n % 10;              if(b ==0)sum += (n/10)*base;          else if(b==1)sum += (n/10)*base + temp%base+1;          else sum += (n/10 + 1)*base;          n = n /10;          base = base * 10;       }       return sum;    }

代码当然可以更加简洁,if else可以替换成简洁的形式,变量声明也可以更简洁,b这个变量也没有必要,所以简洁代码如下

 int NumberOf1Between1AndN_Solution(int n)    {       int t = n, sum =0 , base =1 ;       while(n)       {          sum += (n/10 + ((n%10) >1?1:0))*base + ((n%10)==1?(t%base+1):0);          n /=10;          base *=10;       }       return sum;    }

(^__^) 嘻嘻……

原创粉丝点击