问题描述

    一本书的页码从自然数1开始顺序编码直到自然数n。输的页码按照通常的习惯编排,每个页码都不含有多余的前导数字0。例如,第6页用数字6表示,而不是06或者006等。数字计数问题要求对给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1,2…8,9。

算法设计:给定表示书的总页码的十进制整数n(1<=n<=10^9),计算书的全部页码中分别用到多少次数字0,1,2…8,9。

数据输入:输入数据由文件名为input.txt的文本提供。每个文件只有1行,给出表示书的总页码的整数n。

结果输出:将计算结果输出到文件output.txt。输出文件总共10行,在第k行输出页码中用到数字k-1的次数,k=1,2,…,10。

题解

方法1(暴力求解)

    遍历每一个数字的每位数,对应数字出现的次数+1
代码

public static void count_one(File in, File out) throws FileNotFoundException {
        Scanner sc = new Scanner(in);
        int n=sc.nextInt(); //书的总页码
        int[] arr = new int[10];
        int i,j,m;
        for (i=1;i<=n;i++){
            if (i<10){
                arr[i]++;
            }
            if (i>=10){
                j=i;
                while (j!=0){
                    m=j%10; //求余
                    arr[m]++;
                    j=j/10;
                }
            }
        }
        PrintWriter p = new PrintWriter(out);
        for (i=0;i<10;i++){
            p.println(arr[i]);
        }
        p.close();
        sc.close();
    }

方法2(补0+部分暴力求解)

补0

  n位数字,共有10n种可能(0…0也算),那么0-9每一个数字都出现了n*10n-1次。
  0-9 无效的0个数:1
  00-99 无效的0的个数:10+1(00-09)共有11个0无效
  000-999 无效的0的个数:100+10+1
由此,我们可以发现无效的0个数f(n)
Java stream统计指定数字出现过几次_i++

分区

  将数字n分为几个区,例如将3825分为[000, 999], [1000, 1999], [2000, 2999]三部分。
先忽略后两个分区的最高位,那相当于三个[000,999],那0-9各出现了 3 * 3 * 10(3-1) 次。
  这三个分区中只有第一个分区才有无效的0,个数为f(length-1)
  长度为length的数字n有这样的分区数p=n/(10length-1)
统计最高位
  最高位分别为1,…, p,各自出现了10length-1
剩余部分[3000, 3825]使用暴力求解的方法,即[p*10(length-1), n]
代码

//补0+部分暴力求解
    public static void count_two(File in, File out) throws FileNotFoundException {
        Scanner sc = new Scanner(in);
        int n = sc.nextInt();
        String s = String.valueOf(n);
        int length = s.length();
        int[] arr = new int[10];

        int p = area_num(n,length);//块数
        for (int i=0;i<10;i++){
            arr[i]+=p*fn(length-1);
        }
        //处理分区最高位
        if (p>1){
            for (int i=1;i<p;i++){
                arr[i]+=power(10,length-1);
            }
        }
        int zero = zero_num(length-1);
        arr[0]-=zero;
        //处理剩余的数
        int r = p*power(10,length-1);
        int i,j,m;
        for (i=r;i<=n;i++){
            if (i<10){
                arr[i]++;
            }
            if (i>=10){
                j=i;
                while (j!=0){
                    m=j%10; //求余
                    arr[m]++;
                    j=j/10;
                }
            }
        }
        PrintWriter pw = new PrintWriter(out);
        for (i=0;i<10;i++){
            pw.println(arr[i]);
        }
        pw.close();
        sc.close();
    }
    //求单块分区0-9出现的次数
    public static int fn(int n){
        return n*power(10,n-1);
    }
    //求分区块数
    public static int area_num(int n,int length){
        return n/(power(10,length-1));
    }
    //求无效0的个数
    public static int zero_num(int length){
        if (length==1){
            return 1;
        }
        int num = power(10,length-1)+zero_num(length-1);
        return num;
    }
    //求a^b
    public static int power(int a, int b) {
        return (int) Math.pow(a, b);
    }

运算速度对比

Java stream统计指定数字出现过几次_java_02

总结

  网上还有O(logn)的方法,我没完全看懂,在它基础上加上暴力求解,速度比它慢,但比暴力求解要快。