刷题,求指定范围[l, r]内包含k的数字的个数

4,052 阅读7分钟

题目描述

给定一个范围[l,r],求出这个范围内的满足要求的数字的个数。要求是: 这些数字不能包含某个数字n,其中(1 <= l < r <= 1000000000, 0 <= n < 10)。例如,不能包含数字”1”时,那么数字22、32、4都符合要求,数字2314,1233不符合要求。

暴力遍历

这个题目首先想到的方法肯定是暴力遍历。从 l 开始逐渐递增至 r,挨个判断是否包含数字k。这样的话时间复杂度显然太高了,直接这样提交的话是无法AC的,会显示运行超时。代码如下:

    /**
     * 暴力遍历,会超时
     */
    public static int nativeCount(int left, int right, int n) {
        String str;
        String strN = "" + n;
        int count = 0;
        for (int i=left; i<= right; i++) {
            str = "" + i;
            if (!str.contains(strN)) {
                count += 1;
            }
        }
        return count;
    }

指定位数统计

问题分解

题目中求范围[l,r]内不包含数字 n 的数字的总数目num_ori,它可以直接转化为如下问题 求范围[1,Max( l-1, 1)]和 [1,r]内不包含数字 n 的数字的数目num_l, num_r num_ori = num_r - num_l 它又可以转化为如下问题求范围 [1, num] 内包含数字 n的数字的个数。

这个子问题便是本题求解的核心问题。其中n在等于0和非0情况下的统计存在些微差别。

1. 求范围 [1, num] 内包含n=0的数字的个数

先求得num的位数len,如: 1234,len = 4。然后这个字问题求解又可以分为两个部分:

  1. [1, 99...9(len - 1个9)] 中包含0的数字的个数
  2. [100...0(len - 1个0), num] 中包含0的数字的个数

1.1 [1, 99...9(d个9)] 中包含0的数字的个数

将 [1, 99...9(d个9)] 中包含0的数字的个数记为f(d),f(d)又可以分为两个部分:

  1. [1, 99...9(d - 1个9)] 中包含0的数字的个数,即f(d-1)

  2. [100...0(d - 1个0), 99...9(d个9)]中包含0的数字的个数。

其中第二个部分,首个字母有1~9,9种可能,不过都不包含数字0。而00...0(d - 1个0) ~ 9...9(d - 1个9)中包含0的数字可以分成d - 1种,分别是包含:

  • 1个0,d - 1 - 1个非0数字
  • 2个0,d - 1 - 2个非0数字 ......
  • i个0,d - 1- i个非0数字 ......
  • d - 1个0,d - 1 - (d - 1)个非0数字

所以第二部分的解析表达式为

9\sum_{i=1}^{d-1}9^{d-1-i}C^i_{d-1} = \sum_{i=1}^{d-1}9^{d-i}C^i_{d-1}

所以

f(d) = f(d-1) +  \sum_{i=1}^{d-1}9^{d-i}C^i_{d-1}

1.2 [100...0(len - 1个0), num] 中包含0的数字的个数

这个问题正面计算不太方便可以转化为 [100...0(len - 1个0), num] 中包含0的数字的总数 减去 [100...0(len - 1个0), num] 中不包含0的数字的个数。例如,num = 234。整个数目可以分为以下几个部分:

  • 100 ~ 199中不包含0的数目,(2 - 1) * 9 ^ 2
  • 200 ~ 229中不包含0的数目,(3 - 1) * 9 ^ 1
  • 230 ~ 234中不包含0的数目,(4 - 1) * 9 ^ 0

所以整个数目为

num-10^{len-1}+1- \sum_{i=1}^{len}(右数第i为的数字-1)9^{len-1-i}-1 
= num-10^{len-1}- \sum_{i=1}^{len}(右数第i为的数字-1)9^{len-1-i}

2. 求范围 [1, num] 内包含n, n属于[1, 9]的数字的个数

先求得num的位数len,如: 1234,len = 4。然后这个字问题求解又可以分为两个部分:

  1. [1, 99...9(len - 1个9)] 中包含n, n属于[1, 9]的数字的个数
  2. [100...0(len - 1个0), num] 中包含n, n属于[1, 9]的数字的个数

2.1 [1, 99...9(d个9)] 中包含n, n属于[1, 9]的数字的个数

将 [1, 99...9(d个9)] 中包含n的数字的个数记为h(d),h(d)又可以分为两个部分:

  1. [1, 99...9(d - 1个9)] 中包含n的数字的个数,即h(d-1)

  2. [100...0(d - 1个0), 99...9(d个9)]中包含n的数字的个数。 其中第二个部分,对于最高位high

  • 若high为n,剩下d - 1位可以任意取[0, 9],则有10^(d-1)种情况
  • 若high不为n,剩下d - 1位至少需要包含一个n,则有8 * h(d-1)种情况 所以
h(d) = 10^{d-1} + 9h(d-1)

2.2 [100...0(len - 1个0), num] 中包含n, n属于[1, 9]的数字的个数

这里的统计计算需要根据数字num的最高位和n来区别对待:

假如最高位等于 n,例如 428,那么结果等于下面两部分的和:
  1. 从 1 到 399 的计数,= 4*h(2)
  2. 从 400 到 428 的计数,= 29
假如最高位大于 n,例如 728,那么结果等于下面三部分的和:
  1. 从 1 到 399 的计数加上从 500 到 699 的计数,= 6*h(2)
  2. 从 400 到 499 的计数,= 100
  3. 从 700 到 728 的计数,递归求解 28
如果最高位小于 n,例如 328,那么结果等于下面两部分的和:
  1. 从 1 到 299 的计数,= 3*h(2)
  2. 从 300 到 328 的计数,递归求解 28

完整指定位数统计代码

package huawei.credible;


import java.util.Scanner;

public class Q221NumberCount {
    public static void main(String[] args)
    {
        Scanner s = new Scanner(System.in);
        String line;
        while (s.hasNextLine()) {
            line = s.nextLine();
            String[] items = line.split(" ");
            int left = Integer.parseInt(items[0]);
            int right = Integer.parseInt(items[1]);
            int n = Integer.parseInt(items[2]);
            System.out.println(rangeContainsCount(left, right, n));
        }
    }

    /**
     * 暴力遍历,会超时
     */
    public static int nativeCount(int left, int right, int n) {
        String str;
        String strN = "" + n;
        int count = 0;
        for (int i=left; i<= right; i++) {
            str = "" + i;
            if (!str.contains(strN)) {
                count += 1;
            }
        }
        return count;
    }

    /**
     * 求[left, right]数字中不包含数字input的数字的个数
     */
    public static int rangeContainsCount(int left, int right, int input) {
        int part1 =  left -1 == 0 ? 0 : rangeNotContainsCount(left-1, input);
        int part2 = rangeNotContainsCount(right, input);
        return part2 - part1;
    }

    /**
     * 求 1到num中不包含数字input(0~9)的数字的个数
     */
    public static int rangeNotContainsCount(int num, int input) {
        return num - numContainsCount(num, input);
    }

    /**
     * 求 1到num中包含数字input(0~9)的数字的个数
     */
    public static int numContainsCount(int num, int input) {
        if (input == 0) {
            return numContains0Counts(num);
        } else {
            return calcNonZeroCounts(num, input);
        }
    }

    /**
     * 求 1到num中包含数字0的数字的个数
     */
    public static int numContains0Counts(int num) {
        // num的位数
        int len = ("" + num).length();

        // 1 ~ 99...9(len-1个9)中包含0的数字的个数
        int part1 = calc0CountsInDNine(len - 1);

        // 10...00(len-1个0) ~ num中包含0的数字的个数
        int part2 = calc0Part2(num, len);

        return part1 + part2;
    }

    /**
     * 计算10...00(len-1个0) ~ num中包含0的数字的个数
     */
    public static int calc0Part2(int num, int len) {
        // 10...00(len-1个0) ~ num 之间总的数目
        int res = num - (int)Math.pow(10, len-1) + 1;
        // 10...00(len-1个0) ~ num 之间不包含0的数字的数目
        int temp = 0;
        String strNum = "" + num;
        int high;
        for (int i=0; i<len; i++) {
            high = Integer.parseInt(strNum.substring(i, i+1));
            if (high == 0) {
                temp -= 1;
                break;
            }
            temp += (high - 1) * Math.pow(9, len - 1 -i);
        }
        return res - temp - 1;
    }

    /**
     * 计算 1 ~ 99..9(d个9)中包含数字0的个数记为f(d)
     */
    public static int calc0CountsInDNine(int d) {
        if (d <= 1) {
            return 0;
        } else {
            return calc0CountsInDNine(d-1) + 9 * calcCombSum(d-1);
        }
    }

    public static int calcCombSum(int n) {
        int res = 0;
        for (int i=1; i<=n; i++) {
            res += Math.pow(9, n-i) * C(n, i);
        }
        return res;
    }

    /**
     * 排列数
     */
    public static int A(int n, int m)
    {
        int result = 1;
        // 循环m次,如A(6,2)需要循环2次,6*5
        for (int i = m; i > 0; i--)
        {
            result *= n;
            n--;// 下一次减一
        }
        return result;
    }


    /**
     * 组合数
     */
    public static int C(int n, int m)// 应用组合数的互补率简化计算量
    {
        int helf = n / 2;
        if (m > helf) {
            m = n - m;
        }
        // 分子的排列数
        int numerator = A(n, m);
        // 分母的排列数
        int denominator = A(m, m);
        return numerator / denominator;
    }

    /**
     * 求 1到num中包含数字input(1~9)的数字的个数
     */
    public static int calcNonZeroCounts(int num, int input) {
        if (num < 10) {
            return num >= input ? 1 : 0;
        }
        // num的位数
        int len = ("" + num).length();
        int d = len - 1;
        //  1 ~ 99..9(d个9)中包含数字input(1~9)的个数
        int tmp = calcKCountInDNine(d);

        int res = 0;
        int high = getHighestBit(num);
        int remain = num % (int)Math.pow(10, d);
        if (high == input) {
            res = high * tmp + num % (int)Math.pow(10, d) + 1;
        } else if (high > input) {
            res = (high - 1) * tmp + (int)Math.pow(10, d) + calcNonZeroCounts(remain, input);
        } else {
            res = high * tmp + calcNonZeroCounts(remain, input);
        }

        return res;
    }

    /**
     * 获取num的最高位数字,可以返回0
     */
    public static int getHighestBit(int num) {
        String res = "0" + num;
        return Integer.parseInt(res.substring(1, 2));
    }

    /**
     * 计算 1 ~ 99..9(d个9)中包含数字k(1~9)的个数,记为f(d)
     */
    public static int calcKCountInDNine(int d) {
        if (d <= 0) {
            return 0;
        }
        else if (d == 1) {
            return 1;
        } else {
            return (int) (Math.pow(10, d-1) + 9 * calcKCountInDNine(d - 1));
        }
    }
}