Count of Integers

发布时间 2023-06-04 16:49:53作者: onlyblues

Count of Integers

You are given two numeric strings num1 and num2 and two integers max_sum and min_sum. We denote an integer x to be good if:

  • num1 <= x <= num2
  • min_sum <= digit_sum(x) <= max_sum.

Return the number of good integers. Since the answer may be large, return it modulo 109 + 7.

Note that digit_sum(x) denotes the sum of the digits of x .

Example 1:

Input: num1 = "1", num2 = "12", min_num = 1, max_num = 8
Output: 11
Explanation: There are 11 integers whose sum of digits lies between 1 and 8 are 1,2,3,4,5,6,7,8,10,11, and 12. Thus, we return 11.

Example 2:

Input: num1 = "1", num2 = "5", min_num = 1, max_num = 5
Output: 5
Explanation: The 5 integers whose sum of digits lies between 1 and 5 are 1,2,3,4, and 5. Thus, we return 5.

Constraints:

  • 1 <= num1 <= num2 <= 1022
  • 1 <= min_sum <= max_sum <= 400 

 

解题思路

  很经典的数位dp。现在要求的是在$[\text{num1}, \text{num2}]$范围内的数字中,有多少个数字的各位之和在$[\text{max_sum}, \text{min_sum}]$范围内。为此我们可以先求出$0 \sim \text{num2}$中满足各位之和在$[\text{max_sum}, \text{min_sum}]$范围内的数字个数,记作$b$。再求出$0 \sim \text{num1}-1$中满足各位之和在$[\text{max_sum}, \text{min_sum}]$范围内的数字个数,记作$a$。那么答案就是$b-a$。

  而对于任意数字$x$,如何求出$0 \sim x$中满足各位之和在$[\text{max_sum}, \text{min_sum}]$范围内的数字个数呢?一样用到数位dp。先求出$0 \sim x$中各位之和不超过$\text{max_sum}$的数有多少个,记作$a_2$。再求出$0 \sim x$中各位之和不超过$\text{min_sum} - 1$的数有多少个,记作$a_1$。那么$0 \sim x$中满足各位之和在$[\text{max_sum}, \text{min_sum}]$范围内的数字个数就是$a_2 - a_1$。

  因此最终答案就是$(b_2 - b_1) - (a_2 - a_1)$。

  定义状态$f(i,j,k)$表示所有长度为$i$,且最高位为数字$j$,各位之和不超过$k$的数的集合。根据次高位是哪个数字进行状态划分,因此状态转移方程就是$$f(i,j,k) = \sum\limits_{u=0}^{9}{f(i-1,u,k-j)}$$

  这里用递推的方式来dp。现在求$0 \sim \text{num}$中各位之和不超过$x$的数有多少个。从最高位往底位枚举(从$0$开始),假设当前枚举到第$i$位数字$\text{num}[i]$,并且第$0 \sim i-1$位的数字之和为$s$,那么先累加所有长度为$n-i$,第$i$位是$0 \sim \text{num}[i] - 1$且各位之和为$x - s$的数的个数,即$\sum\limits_{j=0}^{\text{num}[i]-1}{f(n-i,j,x-s)}$。以此类推。当在枚举的过程中发现$s > x$,那么直接返回答案即可。如果枚举完最后的$s \leq x$,那么答案加$1$,因为$\text{num}$也满足要求。

  这样做的计算量为$n \times m \times 10^2$,其中$n$是数字的位数,$m$是各位之和的最大值。如果这样做的话在leetcode会超时,估计卡常了。注意到$n$最大为$23$,因此$m$最大为$23 \times 9 < 400$(所有的数都是$9$的情况),为此我们可以让$m$放小,令$m = \min \{ m, 9 \times n \}$,这样就可以减低计算量了,变成$n^2 \times 10^3$。

  AC代码如下:

 1 class Solution {
 2 public:
 3     int mod = 1e9 + 7;
 4     
 5     int count(string num1, string num2, int min_sum, int max_sum) {
 6         function<int(string, int)> get = [&](string s, int x) {
 7             if (s == "0") return 1; // 特判0的情况
 8             int n = s.size();
 9             x = min(x, 9 * n);
10             vector<vector<vector<int>>> f(n + 1, vector<vector<int>>(10, vector<int>(x + 1)));
11             for (int i = 0; i <= 9; i++) {
12                 for (int j = i; j <= x; j++) {
13                     f[1][i][j]++;
14                 }
15             }
16             for (int i = 2; i <= n; i++) {
17                 for (int j = 0; j <= 9; j++) {
18                     for (int k = j; k <= x; k++) {
19                         for (int u = 0; u <= 9; u++) {
20                             f[i][j][k] = (f[i][j][k] + f[i - 1][u][k - j]) % mod;
21                         }
22                     }
23                 }
24             }
25             int ret = 0, sum = 0;
26             for (int i = 0; i < n; i++) {
27                 int t = s[i] - '0';
28                 for (int j = 0; j < t; j++) {
29                     ret = (ret + f[n - i][j][x - sum]) % mod;
30                 }
31                 sum += t;
32                 if (sum > x) break; // 前面各位之和已经超过x
33             }
34             if (sum <= x) ret++;    // num满足各位之和不超过x
35             return ret;
36         };
37         // 因为要求num1-1,因此对num1进行减1
38         reverse(num1.begin(), num1.end());
39         for (int i = 0; i < num1.size(); i++) {
40             if (num1[i] - 1 >= '0') {   // 不需要借位
41                 num1[i]--;
42                 break;
43             }
44             num1[i] = '9';  // 借位
45         }
46         while (num1.size() > 1 && num1.back() == '0') { // 把前导0删除,同时要至少保留一位数
47             num1.pop_back();
48         }
49         reverse(num1.begin(), num1.end());
50         return ((get(num2, max_sum) - get(num2, min_sum - 1) + mod) % mod - (get(num1, max_sum) - get(num1, min_sum - 1) + mod) % mod + mod) % mod;
51     }
52 };