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
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16589698.html