32. 从1到n的整数中1出现的次数《剑指Offer》(Java版)

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

 

 

 

/**
 * 从1 到 n 中1出现的次数。
 * 例如:求出1~13的整数中1出现的次数
 * 1、10、11、12、13
 * 共6个。
 */
public class _032_NumberOf1Between1AndN_Solution {
    public static void main(String[] args) {
        System.out.println(NumberOf1Between1AndN_Solution(238));
    }

    public static int NumberOf1Between1AndN_Solution(int n) {
        return byWeb(n);
    }

    /**
     * 书上的写法
     */
    private static int byBook(String str) {
        // 判断是否合法.
        if (str == null || str.charAt(0) < '0' || str.charAt(0) > '9') {
            return 0;
        }
        // 获取第一位的数字
        int first = str.charAt(0) - '0';
        // 获取长度
        int len = str.length();
        // str.equals('0')
        if (str.equals('0')) {
            return 0;
        }
        // 如果str.equals('1'-->'9')
        if (len == 1 && first > 0) {
            return 1;
        }

        // 第一位数字为 1 的多少种可能性
        int firstDigit = 0;
        if (first > 1) {
            // 如果不是1,那么直接得到长度,比如 21345 --> 得到  10000
            firstDigit = PowerBase10(len - 1);
        } else if (first == 1) {
            firstDigit = Integer.parseInt(str.substring(1)) + 1;
        }
        // 1346-->21345除了第一位之外的数位中的数目
        // 2 * 4 * 1000     ==>  8000
        int otherDigit = first * (len - 1) * PowerBase10(len - 2);
        // 1    --->    1345
        int numRecursive = byBook(str.substring(1));
        return firstDigit + otherDigit + numRecursive;
    }

    private static int PowerBase10(int n) {
        int res = 1;
        for (int i = 0; i < n; i++) {
            res *= 10;
        }
        return res;
    }

    /**
     * 网上的写法
     */
    private static int byWeb(int n) {
        int ones = 0;
        for (long m = 1; m <= n; m *= 10) {
            // 最终推导公式,我在下面进行还原.
            ones = (int) (ones + (n / m + 8) / 10 * m + (n / m % 10 == 1 ? n % m + 1 : 0));

            /**
             * 我们以 138 举例
             * 个位为1的次数有 : count(130) + count(8)--> 13 + 1
             * 十位为1的次数有 : (count(10)*10 + count(3))*1 -->
             * (不论最后一位是什么,我们只关心第二位是不是1)---> (1 + 1) * 10 --->20
             * 百位为1的次数有 :38种,因为138的百位是1,所以答案为: 138 % 100+1
             * 所以 138 的最终结果为:  14 + 20 + 39 -->73       (我演算过了)
             */

            /**
             * 以2333为例
             * 1. -->若最后一位确定为1,则前面三位共有233种组合,此外,加上额外的2331
             * 那么计算过程如下: (2333 / 10 ) +  3 >= 1 ? 1 : 0  ---> 233 + 1 -->234
             * 10. ---> ( (233 / 10 ) + 3 >= 1 ? 1 : 0) * 10 --->(23)*10 -->240
             * 100. ---> ( (20 / 10) + 3 >= 1 ? 1 : 0) * 100 ---> 300
             * 1000. ---> ((2 / 10) + 2 >= 1 ? 1 : 0) * 1000 ---> 1000
             * 所以最后结果为: 1000+300+240+234 --> 1774       (已演算过)
             */

            /**
             * 也就是说通用公式是
             * if(第一位 < 2),那么只能计算 n % 10000(对应个数的0) + 1.
             * 比如: f(138)--》f(100) + f(38)--》f(100) + 39
             * 比如: f(238)--->f(23) +1 + f(2)*10+10 + f(0)*100+1*100 ---> 24 + 30 + 100->154
             * 特殊处理第一位和最后一位
             */
//            ones = (int) (ones + (n / m + 8) / 10 * m + (n / m % 10 == 1 ? n % m + 1 : 0));
        }
        return ones;
    }
}

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值