leetcode 233.数字1的个数

原题内容:

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

示例:

输入: 13
输出: 6
解释: 数字 1 出现在以下数字中: 1, 10, 11, 12, 13 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-digit-one
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
——————————————分割线-————————————————

我的题解

分析

这个题的题意就是计算出从1到n的这n个整数的10进制的表示里面的数字“1”出现的总次数,总的来说,有两种主要的思路,第一种是一个一个数的数,第二种是根据数字“1”在各个数位出现的规律,运用数学的方法巧算求得。

方法一:暴力法

思路:

很简单的思路,遍历1到n的每一个整数,不断地mod 10,并看余数的值,每一轮的余数是1时,则在计数上面加1
遗憾的是,此方法超时

此方法的java代码示例:
//方法一,暴力遍历,此方法超时
//2020.05.30 19:01
//36 / 40 个通过测试用例 状态:超出时间限制
//最后执行的输入:824883294
class Solution {
    public int countDigitOne(int n) {
        int ans=0;
        for(int i=1;i<=n;i++){
            ans+=jige1(i);
        }return ans;
    }
    public int jige1(int m){
        int one=0;
        while(m>0){
            if(m%10==1){
                one++;
            }
            m/=10;
        }return one;
    }
}

方法二:数学方法找规律快速统计

思路:

想要计算1到n这n个数字里面数字“1”出现的次数,我们不妨依次统计所有数位上面的出现次数。直观的看,在个位上,数字1每10个数出现一次,在十位上,平均每连续100个数里面出现10次(比如说1-100),在百位上,平均每1000个连续数出现100次(比如说1051-2050)……依次类推!
那么我们在大的方向上需要对题目中传入的整数,即“int n”,进行一次次地除以10(取整或者取整加1,具体看其他条件)的操作,以表示,这个n里面包含了多少个数、多少个“连续10个数”、“多少个连续100个数”等等。比如说,n=2323的时候,我们知道在2323个数字里,在2320之前,出现了232个个位上的1,加上2321里面的那个个位上的1,一共是233个也就是2323/10+1个个位的数字1.进一步我们又可以得到,2323个数里面十位上面的1出现的次数出现了2323/100+1段,这个段数乘10就是十位出现1的次数,因为2323/100+1段数字当中,每一段里面有10个数字的十位数字是1……对于普通的数字,以此方法进行计算并求和
但是却有特殊情况,当某一位是0或者1的时候:
例如某一位遇到0,n=2320的时候,我们统计个位1出现的次数,直接2320/10就行,不用加1,因为2320个数里面不包含2321;
其次,某一位遇到1,比方说,n=2313,我们统计十位的1的个数,需要统计1~2310-1(共计231个)以及2310 ~2313的1的个数(具体原因是,这里的十位1不是以整段整段出现的,需要手动把最后一段不完整的个数求出。)

本思路的java代码:
/*方法二,需要考虑n的每一位是0?1?还是大于1的数字?
 *作者@v7fgg
 *执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
 *内存消耗 :36.5 MB, 在所有 Java 提交中击败了33.33%的用户
 *2020年5月31日 17:24
 */
class Solution {
    public int countDigitOne(int n) {
        int ans=0;
        long pOf10=1;//注意不能int,会越界
        int m=n;//复制一个n,因为后面几轮循环后还会涉及到n的原始值
        while(m>0){
            if(m%10==0){
                ans+=m/10*pOf10;                
            }
            else if(m%10>1){
                ans+=(m/10+1)*pOf10;                
            }
            else{//此时m%10==1需要单独考虑
                ans+=m/10*pOf10+(n%(pOf10*10))-pOf10+1;           
            }
            pOf10*=10;
            m/=10;    
        }        
        return ans;
    }
}
本思路精简代码:

另外在判断除以10结果是否加1的那里,我对代码里面的情况进行了合并,运算优化了一些,代码得到了一些精简,上述代码可改为:

/*方法二代码的精简版
 *作者@v7fgg
 *执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
 *内存消耗 :36.3 MB, 在所有 Java 提交中击败了33.33%的用户
 *2020年5月31日 17:33
 */
class Solution {
    public int countDigitOne(int n) {
        int ans=0;
        long pOf10=1;//注意不能int,会越界
        int m=n;//复制一个n,因为后面几轮循环后还会涉及到n的原始值
        while(m>0){
            ans+=m%10!=1?(m+9)/10*pOf10:m/10*pOf10+(n%(pOf10*10))-pOf10+1;
            pOf10*=10;
            m/=10;    
        }        
        return ans;
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可爱抱抱呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值