剑指offer题解java版:整数中1出现的次数(从1到n整数中1出现的次数)

244 阅读3分钟

题目描述

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

对应牛客网剑指offer31题Leetcode:233. Number of Digit One

解题思路

方法1:暴力遍历

遍历k\in[0, n],并将每个k转成字符数组,遍历计数有多少个'1'。时间复杂度为O(nlogn)

    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for (int i=0; i<n; i++) {
            String str = String.valueOf(i);
            for (char item : str.toCharArray()) {
                if (item == '1') {
                    count += 1;
                }
            }
        }
        return count;
    }

在牛客上可以AC,对应的
运行时间:48ms
占用内存:12760k

方法2:指定位数分析

假设n是一个5位数abcde,其中a,b,c,d,e分别是对应位上的数字。整个问题的结果可以划分为5个部分,分别右数第i\in[1, 5]位上数字为1的次数相加。
以i=3,百位数字c为例开始分析,可以发现百位上为1的次数不仅与百位本身有关,也与其前面的高位数字和其后面的低位数字有关。具体的:

  1. 如果c=0,百位上为1的次数为 ab * 100,100是源于ab100 ~ ab199之间有100个数,而高位的ab可以取1 ~ ab 共ab个数。
  2. 如果c=1,百位上为1的次数为 ab * 100 + de + 1,其中de源于ab100 ~ ab1de之间有de+1个数。
  3. 如果c>=1,百位上为1的次数为 (ab+1) * 100,100是源于ab100 ~ ab199之间有100个数,而高位的ab可以取0 ~ ab 共ab个数。
    于是我们可以根据规律给出如下解法,时间复杂度为O(logn)
    public int NumberOf1Between1AndN_Solution(int n) {
        if (n == 0) {
            return 0;
        }
        int count = 0;
        int size = String.valueOf(n).length();
        for (int i=1; i<=size; i++) {
            int val = getIdxVal(n, i);
            int high = getHighVal(n, i);
            int low = getLowVal(n, i);
            double base = Math.pow(10, i-1);
            if (val == 0) {
                count += high * base;
            } else if (val == 1) {
                count += high * base + low + 1;
            } else {
                count += (high + 1) * base;
            }
        }

       return count;
    }

    /**
     * 获取n右数位第i位的数字
     */
    private int getIdxVal(int n, int idx) {
        return (int)((n / Math.pow(10, idx-1)) % 10);
    }

    /**
     * 获取n右数第i位右边的高位数字
     */
    private int getHighVal(int n, int idx) {
        return (int)(n / Math.pow(10, idx));
    }

    /**
     * 获取n右数第i位左边的低位数字
     */
    private int getLowVal(int n, int idx) {
        return (int)(n % Math.pow(10, idx-1));
    }

在牛客上AC对应的
运行时间:20ms
占用内存:9472k

引申思考:100~1300的整数中1出现的次数呢?

这个问题可以借助上面的结果进行计算,先算出0 ~ 100的次数f(100),在算出0 ~ 1300的次数f(1300),结果就是:
f(1300) - f(100) + k(100),k(n)表示n这个数中1出现的次数。代码如下:

    /**
     * 求输入n数字中1出现的次数
     */
    private int numOfOne(int n) {
        int count = 0;
        char[] chars = String.valueOf(n).toCharArray();
        for (char item : chars) {
            if (item == '1') {
                count += 1;
            }
        }
        return count;
    }

    /**
     * 求start到end中整数1出现的次数,包含start和end
     */
    public int NumberOf1BetweenAnd_Solution(int start, int end) {
        if (start > end) {
            throw new IllegalArgumentException();
        }
        return numOfOne(start) + NumberOf1Between1AndN_Solution2(end) - NumberOf1Between1AndN_Solution2(start);
    }

如果觉得有用,就给个赞呗^_^ 如果发现内容有问题,麻烦指正下~