• 《剑指offer》面试题32----从1到n整数中1出现的次数


    题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现了5次。

    解法一:不考虑时间效率的解法(略)

    ps:我感觉是个程序员都能想到这第一种解法,时间复杂度O(nlogn)。这个方法没有什么意义,但是简单易懂,去小公司足够了,这里不讲了。

    解法二:分析数字规律,时间复杂度O(logn).

    这是我写这篇文章的初衷。《剑指offer》洋洋洒洒写了几十行代码,然而在leetcode上大神却只用了5行!当天晚上智障,脑子全是浆糊,竟然没有看懂什么意思=。=,我一度怀疑智商受到了碾压。然而在今天睡眠比较充足,头脑比较清醒的情况下终于理顺了思路~

    其实这道题目很多地方都有讲,包括《编程之美》,但是也有20行左右的代码,没耐心了。其它的一些帖子讲的乱七八糟,这对于我这种爱简洁,爱干净,还有严重强迫症的人是不能忍的,下面强迫症患者要开始装逼了。。。

    先上代码:

     1 package test;
     2 
     3 public class Question_32 {
     4     public static int countDigitOne(int n) {
     5         int ones = 0;
     6         for (long m = 1; m <= n; m *= 10)
     7             ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
     8         return ones;
     9     }
    10 
    11     public static void main(String[] args) {
    12         // TODO Auto-generated method stub
    13         System.out.println(countDigitOne(12));
    14 
    15     }
    16 
    17 }

    核心代码只有line4~line9。leetcode原链接: https://discuss.leetcode.com/topic/18054/4-lines-o-log-n-c-java-python 牛客网链接: https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6 。

    装逼模式开启:

    我们从一个5位的数字讲起,先考虑其百位为1的情况。分3种情况讨论:

    百位数字>=2  example: 31256  当其百位为>=时,有以下这些情况满足(为方便起见,计312为a,56为b):

        100 ~   199

      1100 ~  1199

            .....

     31100 ~ 31199

     余下的都不满足!

    因此,百位>=2的5位数字,其百位为1的情况有(a/10+1)*100个数字   (a/10+1)=>对应于 0 ~ 31,且每一个数字,对应范围是100个数(末尾0-99)

    百位数字 ==1 example: 31156 当其百位为1时,有以下这些情况满足:

         100 ~   199

       1100 ~  1199

              ......

      30100 ~ 30199

      31100 ~ 31156

    因此,百位为1的5位数字,共有(a/10)*100+(b+1)

    百位数字 ==0 example: 31056 当其百位为0时,有以下这些情况满足:

         100 ~   199

       1100 ~  1199

     30100 ~ 30199

      其余都不满足

    因此,百位数为0的5位数字,共有(a/10)*100个数字满足要求

    我们可以进一步统一以下表达方式,即当百位>=2或=0时,有[(a+8)/10]*100,当百位=1时,有[(a+8)/10]*100+(b+1)。用代码表示就是: [(a+8)/10]*100+(a%10==1)?(b+1):0;

    为什么要加8呢?因为只有大于2的时候才会产生进位等价于(a/10+1),当等于0和1时就等价于(a/10)。另外,等于1时要单独加上(b+1),这里我们用a对10取余是否等于1的方式判断该百位是否为1。

    Question:有缺陷或逻辑错误吗?

    有人可能会有疑惑,比如11100,这个数在考虑百位为1的时候算作了一次,在考虑千位的时候也算了一次,在考虑万位为1的时候又算了一次,一共计了3次,这不是明显重复吗?

    我的回答是,不重复!

    分析:题目中要我们统计出现的1的个数,那么我们可以看到11100一共是3个1,如果剔除了重复的情况只考虑一次才会是问题。换言之,在计算从1到n整数中1的出现次数时,我们把10位出现1的情况个数加上百位出现1的情况个数一直加到最高位是1的情况的个数,这里面一个数可能被统计过多次;11100百位出现1,千位和万位都为1,那么被重复统计了3次

    代码分析:

    1 public static int countDigitOne(int n) {
    2         int ones = 0;
    3         for (long m = 1; m <= n; m *= 10)
    4             ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
    5         return ones;
    6     }

    for (long m = 1; m <= n; m *= 10) 在这里的作用是,从个位开始考虑,再到十位,百位,千位,一直到超出这个数!为什么m要用long型呢?因为n可能没有超过整型的表达范围(int刚好可以表示n),而10*m恰恰有可能刚刚超过!ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0); 这里ones用于表示1的个数,当m=100时,n/m其实代表的是a,而n%m代表的是b,此时考虑的是百位为1的情况;当m=1000,自然考虑的就是千位等于1的情况了~ 至于为什么加8,那个三目运算符是干嘛子用的上面都已经讲过了。

    最后,总结一下。这道题网上答案太多了,但是我觉得只有这种方法最让人眼前一亮。抖机灵的不少,比如用java字符串处理的,自以为很厉害,其实根本没含金量(时间复杂度O(nlogn)啊!)关键是这还有赞同的,不知道算法分析是怎么学的。《剑指offer》和《编程之美》的答案可能曾经是最佳,但是现在被更好的方法替换了,而作者并不知情。一本好书看3遍,胜过3本好书看一遍!相信一个月后,我对这道题的印象可能就没有多少了,及时整理,利人利己,温故而知新~

     
  • 相关阅读:
    深入理解JavaScript定时器(续)
    也谈前端基础设施建设
    Reporting Services在指定计算机上找不到报表服务器
    优化tempdb提高SQL Server的性能
    SQL 代理服务未运行。此操作需要 SQL 代理服务。 (rsSchedulerNotResponding) 获取联机帮助
    报表服务器上出现内部错误。有关详细信息,请参阅错误日志。 (rsInternalError) 获取联机帮助.找不到存储过程 'GetOneConfigurationInfo'。
    表中包含有外键时无法进行导入数据,
    SQLSTATE ODBC API(驱动程序管理器)错误
    数据库只能用机器名连接,不能用ip地址连接
    请教:不能访问通过IP访问,却可以通过机器名访问
  • 原文地址:https://www.cnblogs.com/xuanxufeng/p/6854105.html
Copyright © 2020-2023  润新知