今天有点闷,和我的博客说说话吧,这个问题在脑子里萦绕有些久, 总坚信自己有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; }
(*^__^*) 嘻嘻……