📌  相关文章
📜  数字d恰好出现K次的范围内的数字计数(1)

📅  最后修改于: 2023-12-03 15:39:56.698000             🧑  作者: Mango

数字d恰好出现K次的范围内的数字计数

有时候我们需要统计一个数字在一个范围内的出现次数。更具体地说,我们需要计算在一个区间 [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

第二种方法是利用数位 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 的实现过程相对较复杂,但时间复杂度更优秀。在实际使用时应根据具体情况作出取舍。