📅  最后修改于: 2023-12-03 15:39:56.698000             🧑  作者: Mango
有时候我们需要统计一个数字在一个范围内的出现次数。更具体地说,我们需要计算在一个区间 [L, R] 中数字 d 恰好出现 k 次的个数。如何解决这个问题呢?
思路上,我们可以考虑将这个问题转化成更易于处理的形式。我们可以通过计算数字 d 在 [1, R] 中的出现的次数和在 [1, L-1] 中的出现的次数来得到在 [L, R] 中出现 k 次的个数。
具体来说,设函数 count(x) 表示数字 d 在 [1, x] 中的出现次数。则在 [1, R] 中数字 d 出现的次数为 count(R)。同样地,可以计算出在 [1, L-1] 中数字 d 出现的次数为 count(L-1)。最终,我们可以计算出在 [L, R] 中数字 d 出现 k 次的个数为 count(R) - count(L-1)。
接下来,我们需要找到一种有效的方法来计算函数 count(x) 的值。下面介绍两种常见的计算方法。
第一种方法是最朴素的枚举法,它的时间复杂度为 O(log x)。我们可以遍历 [1, x] 中的每一个数字,计算它的个位是否等于 d 并累加。最终得到的结果就是 count(x) 的值。
int count(int x, int d) {
int res = 0;
while (x) {
if (x % 10 == d) {
res++;
}
x /= 10;
}
return res;
}
第二种方法是利用数位 DP 的思想,可以将时间复杂度优化到 O(log x)。
我们可以利用前缀和的思想预处理出 dp 数组,其中 dp[i][j] 表示在不超过 i 的数中,数字 d 出现了 j 次的个数。
预处理的过程是一个数位 DP 的过程,具体实现如下:
int dp[10][10];
void init(int d) {
memset(dp, 0, sizeof(dp));
for (int i = 0; i <= 9; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
dp[i][j] = dp[i-1][j] + (i == d ? dp[i-1][j-1] : 0);
}
}
}
预处理完之后,我们可以根据 count(x) 的定义计算出 dp 数组里的数值,实现代码如下:
int count(int x, int d) {
int res = 0, cnt = 0;
vector<int> digit;
while (x) {
digit.push_back(x % 10);
x /= 10;
}
for (int i = digit.size() - 1; i >= 0; i--) {
int num = digit[i];
for (int j = 0; j < cnt; j++) {
res += dp[i][j];
}
if (num == d) {
cnt++;
}
}
return res + (cnt == k);
}
本文介绍了如何计算在一个区间 [L, R] 中数字 d 恰好出现 k 次的个数。其中,我们介绍了两种常见的计算方法:枚举和数位 DP。数位 DP 的实现过程相对较复杂,但时间复杂度更优秀。在实际使用时应根据具体情况作出取舍。