Skip to content

feat: add solutions to lc problem: No.0357 #3540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 113 additions & 167 deletions solution/0300-0399/0357.Count Numbers with Unique Digits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ tags:
<pre>
<strong>输入:</strong>n = 2
<strong>输出:</strong>91
<strong>解释:</strong>答案应为除去 <code>11、22、33、44、55、66、77、88、99 </code>外,在 0 ≤ x &lt; 100 范围内的所有数字。
<strong>解释:</strong>答案应为除去 <code>11、22、33、44、55、66、77、88、99 </code>外,在 0 ≤ x &lt; 100 范围内的所有数字。
</pre>

<p><strong>示例 2:</strong></p>
Expand All @@ -55,101 +55,7 @@ tags:

<!-- solution:start -->

### 方法一:排列组合

当 $n=0$ 时,有 $0\le x \lt 1$,只有 $1$ 个数字,即 $0$。

当 $n=1$ 时,有 $0\le x \lt 10$,有 $10$ 个数字,即 $0,1,2,3,4,5,6,7,8,9$。

当 $n=2$ 时,有 $0\le x \lt 100$,那么 $x$ 的选择可以由两部分组成:只有一位数的数字和有两位数的数字。对于只有一位数的情况,可以由上述的边界情况计算;对于有两位数的情况,由于第一位数字不能为 $0$,所以第一位数字有 $9$ 种选择,第二位数字有 $9$ 种选择,所以有 $9 \times 9$ 种选择,即 $81$ 种选择。

更一般的情况,含有 $n$ 位数且各位数字都不同的数字 $x$ 的个数为 $9 \times A_{9}^{n-1}$。再加上含有小于 $n$ 位数且各位数字都不同的数字 $x$ 的个数,即为答案。

时间复杂度 $O(n)$。

<!-- tabs:start -->

#### Python3

```python
class Solution:
def countNumbersWithUniqueDigits(self, n: int) -> int:
if n == 0:
return 1
if n == 1:
return 10
ans, cur = 10, 9
for i in range(n - 1):
cur *= 9 - i
ans += cur
return ans
```

#### Java

```java
class Solution {
public int countNumbersWithUniqueDigits(int n) {
if (n == 0) {
return 1;
}
if (n == 1) {
return 10;
}
int ans = 10;
for (int i = 0, cur = 9; i < n - 1; ++i) {
cur *= (9 - i);
ans += cur;
}
return ans;
}
}
```

#### C++

```cpp
class Solution {
public:
int countNumbersWithUniqueDigits(int n) {
if (n == 0) return 1;
if (n == 1) return 10;
int ans = 10;
for (int i = 0, cur = 9; i < n - 1; ++i) {
cur *= (9 - i);
ans += cur;
}
return ans;
}
};
```

#### Go

```go
func countNumbersWithUniqueDigits(n int) int {
if n == 0 {
return 1
}
if n == 1 {
return 10
}
ans := 10
for i, cur := 0, 9; i < n-1; i++ {
cur *= (9 - i)
ans += cur
}
return ans
}
```

<!-- tabs:end -->

<!-- solution:end -->

<!-- solution:start -->

### 方法二:状态压缩 + 数位 DP
### 方法一:状态压缩 + 数位 DP

这道题实际上是求在给定区间 $[l,..r]$ 中,满足条件的数的个数。条件与数的大小无关,而只与数的组成有关,因此可以使用数位 DP 的思想求解。数位 DP 中,数的大小对复杂度的影响很小。

Expand All @@ -163,17 +69,29 @@ $$

这里我们用记忆化搜索来实现数位 DP。从起点向下搜索,到最底层得到方案数,一层层向上返回答案并累加,最后从搜索起点得到最终的答案。

我们根据题目信息,设计函数 $dfs()$,对于本题,我们定义 $dfs(pos, mask, lead)$,答案为 $dfs(len, 0, true)$。
我们根据题目信息,设计一个函数 $\textit{dfs}(i, \textit{mask}, \textit{lead})$,其中:

- 数字 $i$ 表示当前搜索到的位置,我们从高位开始搜索,即 $i = 0$ 表示最高位。
- 数字 $\textit{mask}$ 表示当前数字的状态,即 $\textit{mask}$ 的第 $j$ 位为 $1$ 表示数字 $j$ 已经被使用过。
- 布尔值 $\textit{lead}$ 表示当前是否只包含前导 $0$。

其中
函数的执行过程如下

- `pos` 表示数字的位数,从末位或者第一位开始,一般根据题目的数字构造性质来选择顺序。对于本题,我们选择从高位开始,因此,`pos` 的初始值为 `len`;
- `mask` 表示当前数字选取了哪些数字(状态压缩);
- `lead` 表示当前数字是否含有前导零;
如果 $i$ 超过了数字 $n$ 的长度,即 $i \lt 0$,说明搜索结束,直接返回 $1$。

否则,我们从 $0$ 到 $9$ 枚举位置 $i$ 的数字 $j$,对于每一个 $j$:

- 如果 $\textit{mask}$ 的第 $j$ 位为 $1$,说明数字 $j$ 已经被使用过,直接跳过。
- 如果 $\textit{lead}$ 为真且 $j = 0$,说明当前数字只包含前导 $0$,递归到下一层时,此时 $\textit{lead}$ 仍为真。
- 否则,我们递归到下一层,更新 $\textit{mask}$ 的第 $j$ 位为 $1$,并将 $\textit{lead}$ 更新为假。

最后,我们将所有递归到下一层的结果累加,即为答案。

答案为 $\textit{dfs}(n - 1, 0, \textit{True})$。

关于函数的实现细节,可以参考下面的代码。

时间复杂度 $O(n)$。
时间复杂度 $O(n \times 2^D \times D)$,空间复杂度 $O(n \times 2^D)$。其中 $n$ 为数字 $n$ 的长度,而 $D = 10$。

相似题目:

Expand All @@ -192,55 +110,53 @@ $$
class Solution:
def countNumbersWithUniqueDigits(self, n: int) -> int:
@cache
def dfs(pos, mask, lead):
if pos <= 0:
def dfs(i: int, mask: int, lead: bool) -> int:
if i < 0:
return 1
ans = 0
for i in range(10):
if (mask >> i) & 1:
for j in range(10):
if mask >> j & 1:
continue
if i == 0 and lead:
ans += dfs(pos - 1, mask, lead)
if lead and j == 0:
ans += dfs(i - 1, mask, True)
else:
ans += dfs(pos - 1, mask | (1 << i), False)
ans += dfs(i - 1, mask | 1 << j, False)
return ans

return dfs(n, 0, True)
return dfs(n - 1, 0, True)
```

#### Java

```java
class Solution {
private int[][] dp = new int[10][1 << 11];
private Integer[][] f;

public int countNumbersWithUniqueDigits(int n) {
for (var e : dp) {
Arrays.fill(e, -1);
}
return dfs(n, 0, true);
f = new Integer[n][1 << 10];
return dfs(n - 1, 0, true);
}

private int dfs(int pos, int mask, boolean lead) {
if (pos <= 0) {
private int dfs(int i, int mask, boolean lead) {
if (i < 0) {
return 1;
}
if (!lead && dp[pos][mask] != -1) {
return dp[pos][mask];
if (!lead && f[i][mask] != null) {
return f[i][mask];
}
int ans = 0;
for (int i = 0; i < 10; ++i) {
if (((mask >> i) & 1) == 1) {
for (int j = 0; j <= 9; ++j) {
if ((mask >> j & 1) == 1) {
continue;
}
if (i == 0 && lead) {
ans += dfs(pos - 1, mask, lead);
if (lead && j == 0) {
ans += dfs(i - 1, mask, true);
} else {
ans += dfs(pos - 1, mask | (1 << i), false);
ans += dfs(i - 1, mask | 1 << j, false);
}
}
if (!lead) {
dp[pos][mask] = ans;
f[i][mask] = ans;
}
return ans;
}
Expand All @@ -252,33 +168,33 @@ class Solution {
```cpp
class Solution {
public:
int dp[10][1 << 11];

int countNumbersWithUniqueDigits(int n) {
memset(dp, -1, sizeof dp);
return dfs(n, 0, true);
}

int dfs(int pos, int mask, bool lead) {
if (pos <= 0) {
return 1;
}
if (!lead && dp[pos][mask] != -1) {
return dp[pos][mask];
}
int ans = 0;
for (int i = 0; i < 10; ++i) {
if ((mask >> i) & 1) continue;
if (i == 0 && lead) {
ans += dfs(pos - 1, mask, lead);
} else {
ans += dfs(pos - 1, mask | 1 << i, false);
int f[n + 1][1 << 10];
memset(f, -1, sizeof(f));
auto dfs = [&](auto&& dfs, int i, int mask, bool lead) -> int {
if (i < 0) {
return 1;
}
}
if (!lead) {
dp[pos][mask] = ans;
}
return ans;
if (!lead && f[i][mask] != -1) {
return f[i][mask];
}
int ans = 0;
for (int j = 0; j <= 9; ++j) {
if (mask >> j & 1) {
continue;
}
if (lead && j == 0) {
ans += dfs(dfs, i - 1, mask, true);
} else {
ans += dfs(dfs, i - 1, mask | 1 << i, false);
}
}
if (!lead) {
f[i][mask] = ans;
}
return ans;
};
return dfs(dfs, n - 1, 0, true);
}
};
```
Expand All @@ -287,39 +203,69 @@ public:

```go
func countNumbersWithUniqueDigits(n int) int {
dp := make([][]int, 10)
for i := range dp {
dp[i] = make([]int, 1<<11)
for j := range dp[i] {
dp[i][j] = -1
f := make([][1 << 10]int, n)
for i := range f {
for j := range f[i] {
f[i][j] = -1
}
}
var dfs func(int, int, bool) int
dfs = func(pos, mask int, lead bool) int {
if pos <= 0 {
var dfs func(i, mask int, lead bool) int
dfs = func(i, mask int, lead bool) int {
if i < 0 {
return 1
}
if !lead && dp[pos][mask] != -1 {
return dp[pos][mask]
if !lead && f[i][mask] != -1 {
return f[i][mask]
}
ans := 0
for i := 0; i < 10; i++ {
if ((mask >> i) & 1) == 1 {
for j := 0; j < 10; j++ {
if mask>>j&1 == 1 {
continue
}
if i == 0 && lead {
ans += dfs(pos-1, mask, lead)
if lead && j == 0 {
ans += dfs(i-1, mask, true)
} else {
ans += dfs(pos-1, mask|1<<i, false)
ans += dfs(i-1, mask|1<<j, false)
}
}
if !lead {
dp[pos][mask] = ans
f[i][mask] = ans
}
return ans
}
return dfs(n-1, 0, true)
}
```

#### TypeScript

return dfs(n, 0, true)
```ts
function countNumbersWithUniqueDigits(n: number): number {
const f: number[][] = Array.from({ length: n }, () => Array(1 << 10).fill(-1));
const dfs = (i: number, mask: number, lead: boolean): number => {
if (i < 0) {
return 1;
}
if (!lead && f[i][mask] !== -1) {
return f[i][mask];
}
let ans = 0;
for (let j = 0; j < 10; ++j) {
if ((mask >> j) & 1) {
continue;
}
if (lead && j === 0) {
ans += dfs(i - 1, mask, true);
} else {
ans += dfs(i - 1, mask | (1 << j), false);
}
}
if (!lead) {
f[i][mask] = ans;
}
return ans;
};
return dfs(n - 1, 0, true);
}
```

Expand Down
Loading
Loading