【剑指offer】整数中1出现的次数

前言

这道题目,我做了一个小时,说实话,挺简单的,但是我一开始没有悟透,全是自己想出来的,就很麻烦,从一开始的毫无头绪,到后来的悟透了,这个过程实在是太难了。。
在这里插入图片描述
好了,如图所示,需要计算出一个十进制数中的1的个数,注意这是一个范围性概念,是从1~n中所有的数所包含的全部的1 。

代码

先贴一下代码吧,挺抽象的。

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n) {
        vector<int> x,weight;
        int k=1,num=0,t=n,op=1;
        do{
            x.push_back(t%10);
            t/=10;
            weight.push_back(k);
            k*=10;
        }while(t!=0);//拆分
        for(int i=x.size()-1;i>=0;i--){//按位计算
            if(i!=x.size()-1){
                op=n/(weight[i]*10)+1;
            }
            switch (x[i]){
                case 0:
                    num+=(op-1)*weight[i];break;
                case 1:
                    num+=(n%weight[i])+1+(op-1)*weight[i];break;
                default:
                    num+=weight[i]*op;
            }
        }
        return num;
    }
};

代码就如上图所示,很抽象吧,主要思想其实就两个:拆分、按位计算。下面依次进行说明。

  1. 拆分
    拆分这里,真的很简单,就是一个整数拆分过程。不同的是,这里保存了权重,主要是为了在后面按位计算的时候,再连乘的话,时间消耗就太高了。可以看到,这里的weight数组,第一个元素为1,后面是依次乘10。对应了10的0次方、1次方 等等。
  2. 按位计算
    这里的按位计算是最核心的思想,直到现在我都为我能想出来这种做法而感到惊奇,哈哈哈哈。

按位计算的思想

按位计算这里,主要是用到了switch,把当前的状态分成3种,用case语句隔开。下面主要讲一下吧。

核心思想:
核心思想就是将一个整数挨个拆分。然后按位讨论当前位置是否为1,如果是1的话,那么定位当前位置,在整个数中,当前位置是1的数字有多少个。然后遍历所有位置。将所有的位置相加起来就是所有为1的个数。

这么说可能很抽象,我举个例子,如数5502,现在将此数进行拆分,拆为5、5、0、2

现在考虑第一位,第一位为5,也就是说此位的1已经出现过了,就是由于第一位是5,那么之前的1xxx数一定存在,这时,由于第一位是5,说明,以1开头的1xxx,所有数都存在,也就是个数为此位的权重,0~999,共1000个数,这些数都是以1开头。现在考虑一个特殊情况,就是如果此位为1,不是5,那么说明什么?说明1xxx数不足1000个,因为此数最大才1502,也就是1xxx才503个(要包含1000,即为0的情况),也就是如果此位为1,说明计算这个就不能用权重直接计算了,需要用n对权重取余,对于此情况,需要n(1502)对权重1000取余,得到502,最后再加1,则为此位1的个数。

现在考虑第二位,第二位同样为5,说明情况和第一位一样,1都是全部出现,但是这时需要考虑的一个问题就是,或者说需要明确的一个思想就是,我们是计算当前位的1的个数,我们算的是当前位,与其他位是否为1无关,也就是其他位哪怕是1,也不影响当前位的计数,明确了这个概念之后,再算第二位的时候,就要考虑此位之前的情况,因为在此位5之前,还有一个5,也就是当第二位是5的时候,第一位可以是0、1、2、3、4、5,也就是为5+1=6个,也就是为n除以上一位的权重,获得的是有多少个此位向后的数,举个例子,也就是说5502/1000=5,说明,在502之前的那一位有5+1=6种可能(0~5),现在从当前位向后看,当前位为5,说明1xx所有的数都存在,个数为当前位的权重100,但是不要忘了,当前位之前还有一位,也就是会存在这种情况,01xx,11xx,21xx,31xx,41xx,51xx。也就是算第二位1的次数的时候,需要乘上之前位的倍数。也就是第二位的1的次数为6*100=600个,此时,如果第二位为1(5102),那么说明,当前的51xx不是满的,但是之前的0~4都是满的,也就是需要这么算了,(((5102/1000)+1)-1)*100 + 5102%100 = 502个,

现在考虑第三位,第三位为0,说明第三位是不可能形成1x的形式的,也就是对于551x,是不存在的,因为最高才到5502,但是541x是存在的呀,也就是说1x可以存在00~54组,即55组。且这55组1x都是满个数存在(个数等于权重)也就是这时候就应该算(((5502/100)+1)-1)*10 = 540个

现在考虑第四位,第四位为2,高于1,说明第四位的1出现的次数为权重数目(此时权重为1),并且由于2高于1,说明之前的(5502/10)+1=551组全是满个数,即最后的算式为((5502/10)+1)*1=551个。

则现在统一格式,对于每一位的1,我们可以有如下做法(i呈递减状态):

  1. 当前位x[i]小于1,即为0,说明不存在零头(就是不存在不是满个数的),也就是有(((n/weight[i+1])+1)-1)个满个数的。而所谓的满个数就是个数为weight[i]个,所以对应这个情况,应为(((n/weight[i+1])+1)-1)*weight[i]个。
  2. 当前位x[i]等于1,说明存在零头(也就是存在不是满个数的,需要取余操作),也就是有(((n/weight[i+1])+1)-1)个满个数的,以及n%weight[i]+1个零头,所以对应于这种情况,应为(((n/weight[i+1])+1)-1)*weight[i] + (n%weight[i])+1个
  3. 当前位x[i]大于1,同样说明不存在零头,但是这里的满个数就是所有的小组都是满个数,也就是有((n/weight[i+1])+1)个满个数的,所以对应于这种情况,应为((n/weight[i+1])+1)*weight[i]个

总结

怎么样,这么说就简单多了吧,把后面的部分说成零头,区分零头与满个数,这样算起来就简单多了。

当然我也看到了一个比较傻的办法,就是一个循环,遍历所有的数,然后将每个遍历的数转为string,然后遍历这个string,判断是不是字符1,然后累加,这个方式是最笨的,当然也是最费事的。C++里面的string库里有string to_string(int)函数,将整数转为字符串,Java里面是直接String str = n + “”,将n强制转为字符串,然后用charAt()函数,总之都是最傻的,无脑操作罢了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值