【编程马拉松】【006-统计一】

原创 2016年06月01日 07:40:25

【编程马拉松算法目录>>>】


【006-统计一】【工程下载>>>】


1 题目描述


  NewCode总是力争上游,凡事都要拿第一,所以他对“1”这个数情有独钟。爱屋及乌,他也很喜欢包含1的数,例如10、11、12、……。
  例如:N=2,1、2出现了1个“1”。N=12,1、2、3、4、5、6、7、8、9、10、11、12。出现了5个“1”。你能帮他统计一下整数里有多少个1吗?

1.1 输入描述:


  输入有多组数据,每组数据包含一个正整数n,(1≤n≤2147483647)。

1.2 输出描述:


  对应每组输入,输出从1到n(包含1和n)之间包含数字1的个数。例如11与101包含2个1、1101包含3个1。

1.3 输入例子:


1
9
10
20

1.4 输出例子:


1
1
2
12

2 解题思路


2.1 解法一


  最直接的方法就是从1开始遍历到N,将其中每一个数中含有”1”的个数加起来,就得到了问题的解。

private static long countOne3(long n) {
    long count = 0;
    for (int i = 0, j; i <= n; i++) {
        j = i;
        while (j != 0) {
            if (j % 10 == 1) {
                count++;
            }
            j = j / 10;
        }
    }
    return count;
}

  此方法简单,容易理解,但它的问题是效率,时间复杂度为Ο(NlgN),N比较大的时候,需要耗费很长的时间。

2.2 解法二


  我们重新分析下这个问题,对于任意一个个位数n,只要n≥1,它就包含一个“1”;n<1,即n=0时,则包含的“1”的个数为0。于是我们考虑用分治的思想将任意一个n位数不断缩小规模分解成许多个个位数,这样求解就很方便。
  但是,我们该如何降低规模?仔细分析,我们会发现,任意一个m位数其值为n(假设这个数为x1x2x3xm)中“1”的个位可以分解为两个m-1位数(这两个数是10m1x2x3xm)中“1”的个数的和加上一个与最高位数相关的常数C。例如,f(12)=f(101)+f(1210)+3,其中3是表示最高位为1的数字个数,这里就是10、11、12;f(132)=f(1001)+f(132100)+33,33代表最高位为1的数字的个数,这里就是100~132;f(232)=2f(1001)+f(32)+100,因为232大于199,所以它包括了所有最高位为1的数字即100~199,共100个。
  综上,我们分析得出,最后加的常数C只跟最高位x1是否为1有关。
  当最高位为1时,常数C为原数字n去掉最高位后剩下的数字+1。
当最高位不为1时,常数C为10bit,其中bit为N的位数-1,即m-1。如n=12时,bit=1,n=232时,bit=2。
  于是,我们可以列出递归方程如下(其中n=x1x2x3xmbit=m1

f(n)={f(10bit1)+f(n10bit)+n10bit+1x1f(10bit1)+f(nx110bit)+10bitn10,x1=1n10,x11)

  说明:
  1. 对于(1),f(n)(其中n=x1x2x3xm)可以表示为数字序列(0,1,2,…,n)中1出现的次数,因为最高位为1, n10bit+1表示最高位为1的出现次数(不是1的个数)。对于其它部分1的出现次数还未求出。其它部分成两个部分:
    数字还未到bit位,有(0,1,2,,10bit1)的数字序列,要求其这个序列1出现的次数,使用f(10bit1)求得。
    数字已经到了bit位,最高位为1,有数字序列(10bit,10bit+1,,x1x2x3xbit),因为最高位出现的1的次数已经统计了为n10bit+1,所有只要统计其它位出现的次数,实际就是(0,1,2,,x2x3xbit)序列中1出现的次数,。
1出现的次数为:f(n)=f(10bit1)+f(n10bit)+n10bit+1
  2. 对于(2),最高位为1出现的次数为10bit,不考虑最高位为x_1同时忽略最高位,(0,1,2,,10bit1)序列一共出现了x1次,则1出现的次数为x1f(10bit1)。如果最高位为数字x1,则有序列(0,1,2,,x2x3xbit),1出现的次数为:f(nx110bit)
1出现的次数为:f(n)=x1f(10bit1)+f(nx110bit)+10bit
递归的出口条件为:

f(n)={100<n<10n0

  综合即为:

f(n)=01f(10bit1)+f(n10bit)+n10bit+1x1f(10bit1)+f(nx110bit)+10bitn00<n<10n10,x1=1n10,x11

2.3 解法三


  解法二的优点是不用遍历1~N就可以得到f(N)。经过测试,此算法的运算速度比解法一快了许多许多,。但算法二有一个显著的缺点就是当数字超过某个值时会导致堆栈溢出,无法计算
  解法二告诉我们1~N中“1”的个数跟最高位有关,那我们换个角度思考,给定一个N,我们分析1~N中的数在每一位上出现1的次数的和,看看每一位上“1”出现的个数的和由什么决定。
  1位数的情况:在解法二中已经分析过,大于等于1的时候,有1个,小于1就没有。
  2位数的情况:N=13,个位数出现的1的次数为2,分别为1和11,十位数出现1的次数为4,分别为10、11、12、13,所以f(N)=2+4。N=23,个位数出现的1的次数为3,分别为1、11、21,十位数出现1的次数为10,分别为10~19,f(N)=3+10。
  由此我们发现,个位数出现1的次数不仅和个位数有关,和十位数也有关,如果个位数大于等于1,则个位数出现1的次数为十位数的数字加1;如果个位数为0,个位数出现1的次数等于十位数数字。而十位数上出现1的次数也不仅和十位数相关,也和个位数相关:如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1,假如十位数大于1,则十位数上出现1的次数为10。
  3位数的情况:N=123,个位出现1的个数为13:1、11、21、…、91、101、111、121。十位出现1的个数为20:10~19、110~119。百位出现1的个数为24:100~123。
  我们可以继续分析4位数,5位数,推导出下面一般情况:假设N,我们要计算百位上出现1的次数,将由三部分决定:百位上的数字,百位以上的数字,百位以下的数字。
  如果百位上的数字为0,则百位上出现1的次数仅由更高位决定,比如12013,百位出现1的情况为100~199、1100~1199、2100~2199、…、11100~11199,共1200个。等于更高位数字乘以当前位数,即12*100。
如果百位上的数字大于1,则百位上出现1的次数仅由更高位决定,比如12213,百位出现1的情况为100~199、1100~1199、2100~2199、…、11100~11199、12100~12199共1300个。等于更高位数字加1乘以当前位数,即(12+1)*100。
  如果百位上的数字为1,则百位上出现1的次数不仅受更高位影响,还受低位影响。例如12113,受高位影响出现1的情况(等于更高位数字乘以当前位数):100~199、1100~1199、2100~2199、…、11100~11199,共1200个,但它还受低位影响,出现1的情况是12100~12113,共14个,等于低位数字13+1。
  假设n=x1x2x3xixn,当前处理第i位,f(i)为第i位上1出现的次数则有:

f(n)=(x1xi1)10ni(x1xi1)10ni+(xi+1xn+1)(x1xi1+1)10nixi=0xi=1xi>1

  [0,n]的序列中1出现的总次数是:

i=0nf(i)

3 算法实现


import java.util.Scanner;

/**
 * Author: 王俊超
 * Time: 2016-05-07 12:14
 * CSDN: http://blog.csdn.net/derrantcm
 * Github: https://github.com/Wang-Jun-Chao
 * Declaration: All Rights Reserved !!!
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
//        Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data.txt"));
        while (scanner.hasNext()) {
            long n = scanner.nextLong();
//            System.out.println(countOne(n));
//            System.out.println(countOne2(n));
            System.out.println(countOne3(n));
        }

        scanner.close();
    }

    /**
     * 统计1出现的次数
     * 见word解法一
     *
     * @param n 最大的数字
     * @return 1出现的次数
     */
    private static long countOne(long n) {
        long count = 0;
        for (int i = 0, j; i <= n; i++) {
            j = i;
            while (j != 0) {
                if (j % 10 == 1) {
                    count++;
                }
                j = j / 10;
            }
        }
        return count;
    }

    /**
     * 统计1出现的次数
     * 见word解法二
     *
     * @param n 最大的数字
     * @return 1出现的次数
     */
    private static long countOne2(long n) {
        if (n <= 0) {
            return 0;
        } else if (n < 10) {
            return 1;
        } else {
            long count = 0;

            // 最高位的数字
            long highest = n;
            // 数据位数-1
            int bit = 0;
            //代表最高位的权重,即最高位一个1代表的大小
            int power10 = 1;
            while (highest >= 10) {
                highest /= 10;
                bit++;
                power10 *= 10;
            }

            if (highest == 1) {
                count = countOne(power10 - 1)
                        + countOne(n - power10)
                        + n - power10 + 1;
            } else {
                count = highest * countOne(power10 - 1)
                        + countOne(n - highest * power10)
                        + power10;
            }
            return count;
        }
    }

    /**
     * 【最佳解法】
     * 统计1出现的次数
     * 见word解法三
     *
     * @param n 最大的数字
     * @return 1出现的次数
     */
    private static long countOne3(long n) {
        long count = 0;
        long i = 1;
        long current = 0, after = 0, before = 0;
        while ((n / i) != 0) {
            current = (n / i) % 10;
            before = n / (i * 10);
            after = n - (n / i) * i;

            if (current > 1) {
                count = count + (before + 1) * i;
            } else if (current == 0) {
                count = count + before * i;
            } else if (current == 1) {
                count = count + before * i + after + 1;
            }
            i *= 10;
        }
        return count;
    }
}

4 测试结果


这里写图片描述

5 其它信息


因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

【编程马拉松】【006-统计一】

NewCode总是力争上游,凡事都要拿第一,所以他对“1”这个数情有独钟。爱屋及乌,他也很喜欢包含1的数,例如10、11、12、……。 例如:N=2,1、2出现了1个“1”。N=12,1、2、3、4...

我的编程马拉松

时光荏苒,本科四年就快到终点站了。大学三年多了,我做了什么?思考了许久,总结起来,自己就跑了一场编程马拉松。这场马拉松起点在大一,终点在未来。这场马拉松将贯穿我职业生涯的始终。 起步,入门C 相信...

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

Week Of Code 27

这个比赛是编程马拉松风格的,参赛者需要在七天时间内完成七道题的挑战,每天解锁一题,难度递增。这次我做出来四道题,第五题Hard难度是一个超级大模拟,不爱写了A.Drawing Bookhttps://...

换零钱---编程马拉松

换零钱 时间限制:1秒 空间限制:32768K 题目描述 考虑仅用1分、5分、10分、25分和50分这5种硬币支付某一个给定的金额。例如需要支付11分钱,有一个1分和一个10分、一个1分和一...

鹊桥相会---编程马拉松

鹊桥相会 时间限制:1秒 空间限制:32768K 题目描述 一年一度的七夕又要到了,可歌可泣的牛郎织女又可以在鹊桥相会了。不知道大家有没有雅兴陪nowcoder坐在葡...

【编程马拉松】【017-Emacs计算器】

Emacs号称神的编辑器,它自带了一个计算器。与其他计算器不同,它是基于后缀表达式的,即运算符在操作数的后面。例如“2 3 +”等价于中缀表达式的“2 + 3”。请你实现一个后缀表达式的计算器。输入包...

【编程马拉松】【012-Hero】

【编程马拉松算法目录>>>】【011-Hero】【工程下载>>>】1 题目描述  500年前,NowCoder是我国最卓越的剑客。他英俊潇洒,而且机智过人^_^。 突然有一天,NowCoder心爱的公...

【编程马拉松】【019-一笔画】

咱们来玩一笔画游戏吧,规则是这样的:有一个连通的图,能否找到一个恰好包含了所有的边,并且没有重复的路径。输入包含多组数据。每组数据的第一行包含两个整数n和m (2≤n, m≤1000),其中n是顶点的...

【编程马拉松】【004-包含一】

NowCoder总是力争上游,凡事都要拿第一,所以他对“1”这个数情有独钟。爱屋及乌,他也很喜欢包含1的数,例如10、11、12……。你能帮他统计一下有多少个包含1的正整数。

2013腾讯编程马拉松初赛 20130320第一场1001 小Q系列故事——屌丝的逆袭 简单题

//模板开始 #include #include #include #include #include #include #include #i...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)