题目描述
给定一个范围[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, 99...9(len - 1个9)] 中包含0的数字的个数
- [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, 99...9(d - 1个9)] 中包含0的数字的个数,即f(d-1)
-
[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数字
所以第二部分的解析表达式为
所以
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
所以整个数目为
2. 求范围 [1, num] 内包含n, n属于[1, 9]的数字的个数
先求得num的位数len,如: 1234,len = 4。然后这个字问题求解又可以分为两个部分:
- [1, 99...9(len - 1个9)] 中包含n, n属于[1, 9]的数字的个数
- [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, 99...9(d - 1个9)] 中包含n的数字的个数,即h(d-1)
-
[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)种情况 所以
2.2 [100...0(len - 1个0), num] 中包含n, n属于[1, 9]的数字的个数
这里的统计计算需要根据数字num的最高位和n来区别对待:
假如最高位等于 n,例如 428,那么结果等于下面两部分的和:
- 从 1 到 399 的计数,= 4*h(2)
- 从 400 到 428 的计数,= 29
假如最高位大于 n,例如 728,那么结果等于下面三部分的和:
- 从 1 到 399 的计数加上从 500 到 699 的计数,= 6*h(2)
- 从 400 到 499 的计数,= 100
- 从 700 到 728 的计数,递归求解 28
如果最高位小于 n,例如 328,那么结果等于下面两部分的和:
- 从 1 到 299 的计数,= 3*h(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));
}
}
}