Count Special Integers

Count Special Integers

We call a positive integer special if all of its digits are distinct.

Given a positive integer $n$, return the number of special integers that belong to the interval $[1, n]$.

Example 1:

Input: n = 20
Output: 19
Explanation: All the integers from 1 to 20, except 11, are special. Thus, there are 19 special integers.

Example 2:

Input: n = 5
Output: 5
Explanation: All the integers from 1 to 5 are special.

Example 3:

Input: n = 135
Output: 110
Explanation: There are 110 integers from 1 to 135 that are special.
Some of the integers that are not special are: 22, 114, and 131.

Constraints:

$1 \leq n \leq 2 \times {10}^{9}$

 

解题思路

  经典数位dp题目。先用动态规划预处理求长度为$i$,且首位数字(从左至右)为$k$的没有重复数字出现的数的个数。为了避免重复选择了数字,还需要多开一维来记录选择了哪些数字,用二进制状态来表示。

  因此状态表示为$f(i,j,k)$,表示长度为$i$,选择了的数字的状态为$j$,且首位的数字为$k$的数的个数。根据下一位数字的不同来进行集合的划分,状态转移方程为$$f(i,j,k) = \sum\limits_{u = 0 ~\&\&~ u \ne k}^{9} {f(i-1, j - (1 << k), u)}, ~~ ({\text{同时要满足} j>>k\&1==1 ~\&\&~ j>>u\&1==1})$$

  然后进行数位dp,将$n$的每一位取出来,$num[i]$表示$n$的第$i$位数,最高位为第$0$位。其中$num[0]$不可以是$0$,即不能含有前导$0$的数。

  最后再求首位为$0$的数,枚举长度为$2 \sim m$的数进行累加,$m$为$n$的位数。

  AC代码如下:

 1 class Solution {
 2 public:
 3     int countSpecialNumbers(int n) {
 4         vector<int> num;
 5         while (n) { // 把n的每一位取出来
 6             num.push_back(n % 10);
 7             n /= 10;
 8         }
 9         n = num.size();
10         
11         int f[11][1 << 10][10] = {0};
12         vector<int> mp[11]; // mp[i]包含位数为i且没有重复数字的数所表示的状态
13         for (int i = 0; i < 1 << 10; i++) {
14             int cnt = 0;
15             for (int j = 0; j < 10; j++) {
16                 cnt += i >> j & 1;
17             }
18             mp[cnt].push_back(i);
19         }
20 
21         for (int i = 0; i <= 9; i++) {  // 处理长度为1的边界情况
22             f[1][1 << i][i] = 1;
23         }
24         for (int i = 2; i <= n; i++) {
25             for (int &j : mp[i]) {
26                 for (int k = 0; k <= 9; k++) {
27                     if (j >> k & 1) {   // 状态j的第k位要为1,表示包含数字k
28                         for (int u = 0; u <= 9; u++) {
29                             // 状态j的第u位要为1,表示包含数字u,且不能有重复数字,即u!=k
30                             if (u != k && j >> u & 1) f[i][j][k] += f[i - 1][j - (1 << k)][u];
31                         }
32                     }
33                 }
34             }
35         }
36 
37         int ret = 0, last = 0;  // last是一个状态,表示之前的位都选择了哪些数字
38         for (int i = n - 1; i >= 0; i--) {
39             for (int j = i == n - 1; j < num[i]; j++) { // 首位不能为0
40                 for (int &k : mp[i + 1]) {
41                     if (!(last & k)) ret += f[i + 1][k][j]; // 后面的位选择的数字不能包含之前选过的数字
42                 }
43             }
44 
45             if (last >> num[i] & 1) break;  // num[i]之前已经选过,break
46             last |= 1 << num[i];    // 第i位选择了数字num[i]
47             
48             if (i == 0) ret++;  // 选完了所有位的数,再判断一下n是否满足所有位都不同
49         }
50 
51         // 统计位数小于n的位数的剩下所有数
52         for (int i = 1; i < n; i++) {
53             for (int j = 1; j <= 9; j++) {
54                 for (int &k : mp[i]) {
55                     ret += f[i][k][j];
56                 }
57             }
58         }
59 
60         return ret;
61     }
62 };

  再介绍一种数理统计的方法。

  假设$n$有$m$位,位数小于$m$的数一定小于$n$,因此先统计有多少个位数小于$m$的数满足每一位都不一样,假设一个$k$位数($k<m$),那么满足条件的数有$9 \times 9 \times 8 \times \dots \times 10 - k + 1$(这里$k>1$),其中首位不能选$0$,因此有$9$种选择。当只有一位数那么就有$9$种情况。

  然后再统计位数为$m$的数有多少个是满足条件的。

  AC代码如下:

 1 class Solution {
 2 public:
 3     int countSpecialNumbers(int n) {
 4         vector<int> num;
 5         while (n) {
 6             num.push_back(n % 10);
 7             n /= 10;
 8         }
 9         
10         int ret = 0;
11         for (int i = 1; i < num.size(); i++) {  // 统计位数小于num.size()的个数
12             int t = 9;
13             for (int j = 1, k = 9; j < i; j++, k--) {
14                 t *= k;
15             }
16             ret += t;
17         }
18         
19         int last = 0;
20         reverse(num.begin(), num.end());
21         for (int i = 0; i < num.size(); i++) {  // 统计位数为num.size()的个数
22             for (int j = !i; j < num[i]; j++) {
23                 if (last >> j & 1) continue;
24                 int t = 1;
25                 for (int k = 0, u = 9 - i; k < num.size() - i - 1; k++, u--) {
26                     t *= u;
27                 }
28                 ret += t;
29             }
30             
31             if (last >> num[i] & 1) break;
32             last |= 1 << num[i];
33             
34             if (i == num.size() - 1) ret++;
35         }
36         
37         return ret;
38     }
39 };

 

参考资料

  y总,比赛遇到原题了怎么办?力扣第306场周赛:https://www.bilibili.com/video/BV1wv4y1c71n?spm_id_from=333.337.search-card.all.click

posted @ 2022-08-15 21:31  onlyblues  阅读(32)  评论(0编辑  收藏  举报
Web Analytics