Skip to content

Commit 9952724

Browse files
committed
feat: add solutions to lc problem: No.1012
No.1012.Numbers With Repeated Digits
1 parent 4d43f5e commit 9952724

File tree

7 files changed

+349
-221
lines changed

7 files changed

+349
-221
lines changed

solution/1000-1099/1012.Numbers With Repeated Digits/README.md

Lines changed: 104 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,29 @@
4545

4646
<!-- 这里可写通用的实现逻辑 -->
4747

48-
**方法一:数位 DP**
48+
**方法一:状态压缩 + 数位 DP**
4949

50-
题目求解 $[1,n]$ 范围内至少有 $1$ 位重复数字的正整数个数,我们可以转换为求解无重复数字的正整数个数 $cnt$,那么 $n-cnt$ 就是答案
50+
题目要求统计 $[1,..n]$ 中至少有一位重复的数字的个数,我们可以换一种思路,用一个函数 $f(n)$ 统计 $[1,..n]$ 中没有重复数字的个数,那么答案就是 $n - f(n)$
5151

52-
接下来我们就来求解 $[1,n]$ 范围内无重复数字的正整数个数
52+
另外,我们可以用一个二进制数来记录数字中出现过的数字,比如数字中出现了 $1$, $2$, $4$,那么对应的二进制数就是 $\underline{1}0\underline{1}\underline{1}0$
5353

54-
定义 $m$ 表示数字 $n$ 的位数。我们可以将数字分成两类:(1) 数字位数小于 $m$;(2) 数字位数等于 $m$
54+
接下来,我们用记忆化搜索来实现数位 DP。从起点向下搜索,到最底层得到方案数,一层层向上返回答案并累加,最后从搜索起点得到最终的答案
5555

56-
对于第一类,我们可以枚举数字的位数 $i$,其中 $i∈[1,m)$,第一位的数字不为 $0$,有 $[1,9]$ 可选,共 $9$ 种可能。剩余需要选择 $i-1$ 位数字,可选数字为 $[0,9]$ 的数字中除去第一位,共 $9$ 种可能。因此,第一类的数字共有
56+
基本步骤如下
5757

58-
$$
59-
\sum \limits_{i=1}^{m-1} 9\times A_{9}^{i-1}
60-
$$
58+
1. 将数字 $n$ 转为整型数组 $nums$,其中 $nums[0]$ 为最低位,而 $nums[i]$ 为最高位;
59+
1. 根据题目信息,设计函数 $dfs()$,对于本题,我们定义 $dfs(pos, mask, lead, limit)$,其中:
6160

62-
对于第二类,数字的位数等于 $m$,我们从 $n$ 的高位(即 $i=m-1$)开始处理。不妨设 $n$ 当前位的数字为 $v$。
61+
- 参数 $pos$ 表示当前搜索到的数字的位数,从末位或者第一位开始,一般根据题目的数字构造性质来选择顺序。对于本题,我们选择从高位开始,因此,$pos$ 的初始值为数字的高位下标;
62+
- 参数 $mask$ 表示当前数字中出现过的数字;
63+
- 参数 $lead$ 表示当前数字是否仅包含前导零;
64+
- 参数 $limit$ 表示当前可填的数字的限制,如果无限制,那么可以选择 $i \in [0,1,..9]$,否则,只能选择 $i \in [0,..nums[pos]]$。如果 $limit$ 为 `true` 且已经取到了能取到的最大值,那么下一个 $limit$ 同样为 `true`;如果 $limit$ 为 `true` 但是还没有取到最大值,或者 $limit$ 为 `false`,那么下一个 $limit$ 为 `false`
6365

64-
如果当前是 $n$ 的最高一位,那么数字不能为 $0$,可选数字为 $[1,v)$,否则可选数字为 $[0,v)$。若当前可选数字 $j$,那么剩余低位可选的数字总共有 $A_{10-(m-i)}^{i}$,累加到答案中
66+
答案为 $dfs(0, 0, true, true)$
6567

66-
以上我们算的是可选数字小于 $v$ 的情况,若等于 $v$,则需要继续外层循环,继续处理下一位。如果数字 $n$ 所有位均不重复,则 $n$ 本身也是一个特殊整数,需要累加到答案中
68+
关于函数的实现细节,可以参考下面的代码
6769

68-
时间复杂度 $O(m^2)$,其中 $m$ 是数字 $n$ 的位数,这里我们假定 $A_{m}^{n}$ 可以 $O(1)$ 时间算出
70+
时间复杂度 $O(m \times 2^m \times 10)$,空间复杂度 $O(m \times 2^m)$。其中 $m$ 为数字 $n$ 的位数
6971

7072
相似题目:
7173

@@ -117,29 +119,27 @@ class Solution:
117119
def numDupDigitsAtMostN(self, n: int) -> int:
118120
return n - self.f(n)
119121

120-
def f(self, n):
122+
def f(self, n: int) -> int:
121123
@cache
122-
def dfs(pos, mask, lead, limit):
123-
if pos <= 0:
124-
return lead ^ 1
125-
up = a[pos] if limit else 9
124+
def dfs(pos: int, mask: int, lead: bool, limit: bool) -> int:
125+
if pos < 0:
126+
return int(lead) ^ 1
127+
up = nums[pos] if limit else 9
126128
ans = 0
127129
for i in range(up + 1):
128-
if (mask >> i) & 1:
130+
if mask >> i & 1:
129131
continue
130132
if i == 0 and lead:
131133
ans += dfs(pos - 1, mask, lead, limit and i == up)
132134
else:
133135
ans += dfs(pos - 1, mask | 1 << i, False, limit and i == up)
134136
return ans
135137

136-
a = [0] * 11
137-
l = 0
138+
nums = []
138139
while n:
139-
l += 1
140-
a[l] = n % 10
140+
nums.append(n % 10)
141141
n //= 10
142-
return dfs(l, 0, True, True)
142+
return dfs(len(nums) - 1, 0, True, True)
143143
```
144144

145145
### **Java**
@@ -191,36 +191,32 @@ class Solution {
191191

192192
```java
193193
class Solution {
194-
private int[] a = new int[11];
195-
private int[][] dp = new int[11][1 << 11];
194+
private int[] nums = new int[11];
195+
private Integer[][] dp = new Integer[11][1 << 11];
196196

197197
public int numDupDigitsAtMostN(int n) {
198198
return n - f(n);
199199
}
200200

201201
private int f(int n) {
202-
for (var e : dp) {
203-
Arrays.fill(e, -1);
204-
}
205-
int len = 0;
206-
while (n > 0) {
207-
a[++len] = n % 10;
208-
n /= 10;
202+
int i = -1;
203+
for (; n > 0; n /= 10) {
204+
nums[++i] = n % 10;
209205
}
210-
return dfs(len, 0, true, true);
206+
return dfs(i, 0, true, true);
211207
}
212208

213209
private int dfs(int pos, int mask, boolean lead, boolean limit) {
214-
if (pos <= 0) {
210+
if (pos < 0) {
215211
return lead ? 0 : 1;
216212
}
217-
if (!lead && !limit && dp[pos][mask] != -1) {
213+
if (!lead && !limit && dp[pos][mask] != null) {
218214
return dp[pos][mask];
219215
}
220-
int up = limit ? a[pos] : 9;
221216
int ans = 0;
217+
int up = limit ? nums[pos] : 9;
222218
for (int i = 0; i <= up; ++i) {
223-
if (((mask >> i) & 1) == 1) {
219+
if ((mask >> i & 1) == 1) {
224220
continue;
225221
}
226222
if (i == 0 && lead) {
@@ -285,34 +281,36 @@ public:
285281
```cpp
286282
class Solution {
287283
public:
288-
int a[11];
289-
int dp[11][1 << 11];
290-
291284
int numDupDigitsAtMostN(int n) {
292285
return n - f(n);
293286
}
294287
288+
private:
289+
int nums[11];
290+
int dp[11][1 << 11];
291+
295292
int f(int n) {
296-
memset(dp, -1, sizeof dp);
297-
int len = 0;
298-
while (n) {
299-
a[++len] = n % 10;
300-
n /= 10;
293+
memset(dp, -1, sizeof(dp));
294+
int i = -1;
295+
for (; n; n /= 10) {
296+
nums[++i] = n % 10;
301297
}
302-
return dfs(len, 0, true, true);
298+
return dfs(i, 0, true, true);
303299
}
304300
305301
int dfs(int pos, int mask, bool lead, bool limit) {
306-
if (pos <= 0) {
302+
if (pos < 0) {
307303
return lead ? 0 : 1;
308304
}
309305
if (!lead && !limit && dp[pos][mask] != -1) {
310306
return dp[pos][mask];
311307
}
312-
int up = limit ? a[pos] : 9;
308+
int up = limit ? nums[pos] : 9;
313309
int ans = 0;
314310
for (int i = 0; i <= up; ++i) {
315-
if ((mask >> i) & 1) continue;
311+
if (mask >> i & 1) {
312+
continue;
313+
}
316314
if (i == 0 && lead) {
317315
ans += dfs(pos - 1, mask, lead, limit && i == up);
318316
} else {
@@ -382,23 +380,19 @@ func numDupDigitsAtMostN(n int) int {
382380
}
383381

384382
func f(n int) int {
385-
a := make([]int, 11)
386-
dp := make([][]int, 11)
383+
nums := []int{}
384+
for ; n > 0; n /= 10 {
385+
nums = append(nums, n%10)
386+
}
387+
dp := [11][1 << 11]int{}
387388
for i := range dp {
388-
dp[i] = make([]int, 1<<11)
389389
for j := range dp[i] {
390390
dp[i][j] = -1
391391
}
392392
}
393-
l := 0
394-
for n > 0 {
395-
l++
396-
a[l] = n % 10
397-
n /= 10
398-
}
399393
var dfs func(int, int, bool, bool) int
400-
dfs = func(pos, mask int, lead, limit bool) int {
401-
if pos <= 0 {
394+
dfs = func(pos int, mask int, lead bool, limit bool) int {
395+
if pos < 0 {
402396
if lead {
403397
return 0
404398
}
@@ -407,13 +401,13 @@ func f(n int) int {
407401
if !lead && !limit && dp[pos][mask] != -1 {
408402
return dp[pos][mask]
409403
}
410-
ans := 0
411404
up := 9
412405
if limit {
413-
up = a[pos]
406+
up = nums[pos]
414407
}
408+
ans := 0
415409
for i := 0; i <= up; i++ {
416-
if ((mask >> i) & 1) == 1 {
410+
if mask>>i&1 == 1 {
417411
continue
418412
}
419413
if i == 0 && lead {
@@ -427,8 +421,54 @@ func f(n int) int {
427421
}
428422
return ans
429423
}
424+
return dfs(len(nums)-1, 0, true, true)
425+
}
426+
```
427+
428+
### **TypeScript**
430429

431-
return dfs(l, 0, true, true)
430+
```ts
431+
function numDupDigitsAtMostN(n: number): number {
432+
return n - f(n);
433+
}
434+
435+
function f(n: number): number {
436+
const nums: number[] = [];
437+
let i = -1;
438+
for (; n; n = Math.floor(n / 10)) {
439+
nums[++i] = n % 10;
440+
}
441+
const dp = Array.from({ length: 11 }, () => Array(1 << 11).fill(-1));
442+
const dfs = (
443+
pos: number,
444+
mask: number,
445+
lead: boolean,
446+
limit: boolean,
447+
): number => {
448+
if (pos < 0) {
449+
return lead ? 0 : 1;
450+
}
451+
if (!lead && !limit && dp[pos][mask] !== -1) {
452+
return dp[pos][mask];
453+
}
454+
const up = limit ? nums[pos] : 9;
455+
let ans = 0;
456+
for (let i = 0; i <= up; ++i) {
457+
if ((mask >> i) & 1) {
458+
continue;
459+
}
460+
if (lead && i === 0) {
461+
ans += dfs(pos - 1, mask, lead, limit && i === up);
462+
} else {
463+
ans += dfs(pos - 1, mask | (1 << i), false, limit && i === up);
464+
}
465+
}
466+
if (!lead && !limit) {
467+
dp[pos][mask] = ans;
468+
}
469+
return ans;
470+
};
471+
return dfs(i, 0, true, true);
432472
}
433473
```
434474

0 commit comments

Comments
 (0)