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:

1n2×109

 

解题思路

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

  因此状态表示为f(i,j,k),表示长度为i,选择了的数字的状态为j,且首位的数字为k的数的个数。根据下一位数字的不同来进行集合的划分,状态转移方程为f(i,j,k)=u=0 && uk9f(i1,j(1<<k),u),  (同时要满足j>>k&1==1 && j>>u&1==1)

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

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

  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 };
复制代码

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

  假设nm位,位数小于m的数一定小于n,因此先统计有多少个位数小于m的数满足每一位都不一样,假设一个k位数(k<m),那么满足条件的数有9×9×8××10k+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 @   onlyblues  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示