Skip to content

Commit 38abece

Browse files
committed
feat: add solutions to lcof problem: No.43
1 parent 4d07a0f commit 38abece

File tree

5 files changed

+240
-98
lines changed

5 files changed

+240
-98
lines changed

lcof/面试题43. 1~n整数中1出现的次数/README.md

+142-58
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,43 @@
3737

3838
<!-- 这里可写通用的实现逻辑 -->
3939

40-
将 n 拆为两部分:最高位 high 和低位 lows。按 high 是否为 1 分别递归求解结果 f(n)。
40+
**方法一:数位 DP**
4141

42-
以 n=3356 举例说明
42+
这道题实际上是求在给定区间 $[l,..r]$ 中,数字中出现 $1$ 个数。个数与数的位数以及每一位上的数字有关。我们可以用数位 DP 的思路来解决这道题。数位 DP 中,数的大小对复杂度的影响很小
4343

44-
high=3,lows=356,base=1000。此时 n 可拆分为 `0~999`,`1000~1999`,`2000~2999`,`3000~3356`,其中
44+
对于区间 $[l,..r]$ 问题,我们一般会将其转化为 $[1,..r]$ 然后再减去 $[1,..l - 1]$ 的问题,即
4545

46-
- 0~999 范围内 1 的个数为 f(base-1)
47-
- 1000~1999 范围内 1 的个数可分为两部分:千位、其余位。千位都为 1,所以 1 的个数为 base+f(base-1)
48-
- 2000~2999 范围内 1 的个数为 f(base-1)
49-
- 3000~3356 范围内 1 的个数为 f(lows)
46+
$$
47+
ans = \sum_{i=1}^{r} ans_i - \sum_{i=1}^{l-1} ans_i
48+
$$
5049

51-
因此,1 的总个数为 `high*f(base-1)+f(lows)+base`
50+
不过对于本题而言,我们只需要求出区间 $[1,..r]$ 的值即可
5251

53-
最高位非 1 的情况,也可以按照同样的方法分析。
52+
这里我们用记忆化搜索来实现数位 DP。从起点向下搜索,到最底层得到方案数,一层层向上返回答案并累加,最后从搜索起点得到最终的答案。
53+
54+
基本步骤如下:
55+
56+
1. 将数字 $n$ 转为 int 数组 $a$,其中 $a[0]$ 为最低位,而 $a[i]$ 为最高位;
57+
1. 根据题目信息,设计函数 $dfs()$,对于本题,我们定义 $dfs(pos, cnt, limit)$,其中:
58+
59+
- `pos` 表示数字的位数,从末位或者第一位开始,一般根据题目的数字构造性质来选择顺序。对于本题,我们选择从高位开始,因此,`pos` 的初始值为 `len`
60+
- `cnt` 表示当前数字中包含的 $1$ 的个数。
61+
- `limit` 表示可填的数字的限制,如果无限制,那么可以选择 $[0,1,..9]$,否则,只能选择 $[0,..a[pos]]$。如果 `limit``true` 且已经取到了能取到的最大值,那么下一个 `limit` 同样为 `true`;如果 `limit``true` 但是还没有取到最大值,或者 `limit``false`,那么下一个 `limit``false`
62+
63+
那么答案为 $dfs(i, 0, true)$。
64+
65+
关于函数的实现细节,可以参考下面的代码。
66+
67+
时间复杂度 $O(\log n)$。
68+
69+
相似题目:
70+
71+
- [357. 统计各位数字都不同的数字个数](/solution/0300-0399/0357.Count%20Numbers%20with%20Unique%20Digits/README.md)
72+
- [600. 不含连续 1 的非负整数](/solution/0600-0699/0600.Non-negative%20Integers%20without%20Consecutive%20Ones/README.md)
73+
- [788. 旋转数字](/solution/0700-0799/0788.Rotated%20Digits/README.md)
74+
- [902. 最大为 N 的数字组合](/solution/0900-0999/0902.Numbers%20At%20Most%20N%20Given%20Digit%20Set/README.md)
75+
- [1012. 至少有 1 位重复的数字](/solution/1000-1099/1012.Numbers%20With%20Repeated%20Digits/README.md)
76+
- [2376. 统计特殊整数](/solution/2300-2399/2376.Count%20Special%20Integers/README.md)
5477

5578
<!-- tabs:start -->
5679

@@ -60,19 +83,22 @@ high=3,lows=356,base=1000。此时 n 可拆分为 `0~999`,`1000~1999`,`2000~2999
6083

6184
```python
6285
class Solution:
63-
@cache
6486
def countDigitOne(self, n: int) -> int:
65-
if n < 1:
66-
return 0
67-
s = str(n)
68-
high = int(s[0])
69-
base = pow(10, len(s) - 1)
70-
lows = n % base
71-
return (
72-
self.countDigitOne(base - 1) + lows + 1 + self.countDigitOne(lows)
73-
if high == 1
74-
else high * self.countDigitOne(base - 1) + base + self.countDigitOne(lows)
75-
)
87+
@cache
88+
def dfs(pos, cnt, limit):
89+
if pos < 0:
90+
return cnt
91+
up = a[pos] if limit else 9
92+
ans = 0
93+
for i in range(up + 1):
94+
ans += dfs(pos - 1, cnt + (i == 1), limit and i == up)
95+
return ans
96+
97+
a = []
98+
while n:
99+
a.append(n % 10)
100+
n //= 10
101+
return dfs(len(a) - 1, 0, True)
76102
```
77103

78104
### **Java**
@@ -81,17 +107,104 @@ class Solution:
81107

82108
```java
83109
class Solution {
110+
private int[] a = new int[12];
111+
private Integer[][] f = new Integer[12][12];
112+
84113
public int countDigitOne(int n) {
85-
if (n < 1) {
86-
return 0;
114+
int i = -1;
115+
for (; n > 0; n /= 10) {
116+
a[++i] = n % 10;
117+
}
118+
return dfs(i, 0, true);
119+
}
120+
121+
private int dfs(int pos, int cnt, boolean limit) {
122+
if (pos < 0) {
123+
return cnt;
124+
}
125+
if (!limit && f[pos][cnt] != null) {
126+
return f[pos][cnt];
127+
}
128+
int up = limit ? a[pos] : 9;
129+
int ans = 0;
130+
for (int i = 0; i <= up; ++i) {
131+
ans += dfs(pos - 1, cnt + (i == 1 ? 1 : 0), limit && i == up);
132+
}
133+
return f[pos][cnt] = ans;
134+
}
135+
}
136+
```
137+
138+
### **C++**
139+
140+
```cpp
141+
class Solution {
142+
public:
143+
int countDigitOne(int n) {
144+
int a[12]{};
145+
int f[12][12];
146+
memset(f, -1, sizeof f);
147+
int i = -1;
148+
for (; n; n /= 10) {
149+
a[++i] = n % 10;
87150
}
88-
String s = String.valueOf(n);
89-
int high = s.charAt(0) - '0'; // 最高位
90-
int base = (int) Math.pow(10, s.length() - 1); // 基数
91-
int lows = n % base; // 低位
92-
return high == 1 ? countDigitOne(base - 1) + countDigitOne(lows) + lows + 1
93-
: high * countDigitOne(base - 1) + countDigitOne(lows) + base;
151+
function<int(int, int, bool)> dfs = [&](int pos, int cnt, bool limit) -> int {
152+
if (pos < 0) {
153+
return cnt;
154+
}
155+
if (!limit && f[pos][cnt] != -1) {
156+
return f[pos][cnt];
157+
}
158+
int up = limit ? a[pos] : 9;
159+
int ans = 0;
160+
for (int i = 0; i <= up; ++i) {
161+
ans += dfs(pos - 1, cnt + (i == 1), limit && i == up);
162+
}
163+
return f[pos][cnt] = ans;
164+
};
165+
return dfs(i, 0, true);
94166
}
167+
};
168+
```
169+
170+
```go
171+
func countDigitOne(n int) int {
172+
a := [12]int{}
173+
f := [12][12]int{}
174+
for i := range f {
175+
for j := range f[i] {
176+
f[i][j] = -1
177+
}
178+
}
179+
i := -1
180+
for ; n > 0; n /= 10 {
181+
i++
182+
a[i] = n % 10
183+
}
184+
var dfs func(int, int, bool) int
185+
dfs = func(pos, cnt int, limit bool) int {
186+
if pos < 0 {
187+
return cnt
188+
}
189+
if !limit && f[pos][cnt] != -1 {
190+
return f[pos][cnt]
191+
}
192+
up := 9
193+
if limit {
194+
up = a[pos]
195+
}
196+
ans := 0
197+
for i := 0; i <= up; i++ {
198+
t := 0
199+
if i == 1 {
200+
t++
201+
}
202+
ans += dfs(pos-1, cnt+t, limit && i == up)
203+
}
204+
f[pos][cnt] = ans
205+
return ans
206+
}
207+
return dfs(i, 0, true)
95208
}
96209
```
97210

@@ -125,35 +238,6 @@ var countDigitOne = function (n) {
125238
};
126239
```
127240

128-
### **C++**
129-
130-
```cpp
131-
class Solution {
132-
public:
133-
int countDigitOne(int n) {
134-
long long digit = 1;
135-
int count = 0;
136-
int high = n / 10;
137-
int cur = n % 10;
138-
int low = 0;
139-
while (high != 0 || cur != 0) {
140-
if (cur == 0) {
141-
count += high * digit;
142-
} else if (cur == 1) {
143-
count += high * digit + low + 1;
144-
} else {
145-
count += (high + 1) * digit;
146-
}
147-
low += cur * digit;
148-
cur = high % 10;
149-
high /= 10;
150-
digit *= 10;
151-
}
152-
return count;
153-
}
154-
};
155-
```
156-
157241
### **C#**
158242

159243
```cs
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
class Solution {
22
public:
33
int countDigitOne(int n) {
4-
long long digit = 1;
5-
int count = 0;
6-
int high = n / 10;
7-
int cur = n % 10;
8-
int low = 0;
9-
while (high != 0 || cur != 0) {
10-
if (cur == 0) {
11-
count += high * digit;
12-
} else if (cur == 1) {
13-
count += high * digit + low + 1;
14-
} else {
15-
count += (high + 1) * digit;
16-
}
17-
low += cur * digit;
18-
cur = high % 10;
19-
high /= 10;
20-
digit *= 10;
4+
int a[12]{};
5+
int f[12][12];
6+
memset(f, -1, sizeof f);
7+
int i = -1;
8+
for (; n; n /= 10) {
9+
a[++i] = n % 10;
2110
}
22-
return count;
11+
function<int(int, int, bool)> dfs = [&](int pos, int cnt, bool limit) -> int {
12+
if (pos < 0) {
13+
return cnt;
14+
}
15+
if (!limit && f[pos][cnt] != -1) {
16+
return f[pos][cnt];
17+
}
18+
int up = limit ? a[pos] : 9;
19+
int ans = 0;
20+
for (int i = 0; i <= up; ++i) {
21+
ans += dfs(pos - 1, cnt + (i == 1), limit && i == up);
22+
}
23+
return f[pos][cnt] = ans;
24+
};
25+
return dfs(i, 0, true);
2326
}
24-
};
27+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
func countDigitOne(n int) int {
2+
a := [12]int{}
3+
f := [12][12]int{}
4+
for i := range f {
5+
for j := range f[i] {
6+
f[i][j] = -1
7+
}
8+
}
9+
i := -1
10+
for ; n > 0; n /= 10 {
11+
i++
12+
a[i] = n % 10
13+
}
14+
var dfs func(int, int, bool) int
15+
dfs = func(pos, cnt int, limit bool) int {
16+
if pos < 0 {
17+
return cnt
18+
}
19+
if !limit && f[pos][cnt] != -1 {
20+
return f[pos][cnt]
21+
}
22+
up := 9
23+
if limit {
24+
up = a[pos]
25+
}
26+
ans := 0
27+
for i := 0; i <= up; i++ {
28+
t := 0
29+
if i == 1 {
30+
t++
31+
}
32+
ans += dfs(pos-1, cnt+t, limit && i == up)
33+
}
34+
f[pos][cnt] = ans
35+
return ans
36+
}
37+
return dfs(i, 0, true)
38+
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
class Solution {
2+
private int[] a = new int[12];
3+
private Integer[][] f = new Integer[12][12];
4+
25
public int countDigitOne(int n) {
3-
if (n < 1) {
4-
return 0;
5-
}
6-
String s = String.valueOf(n);
7-
int high = s.charAt(0) - '0'; // 最高位
8-
int base = (int) Math.pow(10, s.length() - 1); // 基数
9-
int lows = n % base; // 低位
10-
return high == 1 ? countDigitOne(base - 1) + countDigitOne(lows) + lows + 1
11-
: high * countDigitOne(base - 1) + countDigitOne(lows) + base;
6+
int i = -1;
7+
for (; n > 0; n /= 10) {
8+
a[++i] = n % 10;
9+
}
10+
return dfs(i, 0, true);
11+
}
12+
13+
private int dfs(int pos, int cnt, boolean limit) {
14+
if (pos < 0) {
15+
return cnt;
16+
}
17+
if (!limit && f[pos][cnt] != null) {
18+
return f[pos][cnt];
19+
}
20+
int up = limit ? a[pos] : 9;
21+
int ans = 0;
22+
for (int i = 0; i <= up; ++i) {
23+
ans += dfs(pos - 1, cnt + (i == 1 ? 1 : 0), limit && i == up);
24+
}
25+
return f[pos][cnt] = ans;
1226
}
1327
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
class Solution:
2-
@cache
32
def countDigitOne(self, n: int) -> int:
4-
if n < 1:
5-
return 0
6-
s = str(n)
7-
high = int(s[0])
8-
base = pow(10, len(s) - 1)
9-
lows = n % base
10-
return (
11-
self.countDigitOne(base - 1) + lows + 1 + self.countDigitOne(lows)
12-
if high == 1
13-
else high * self.countDigitOne(base - 1) + base + self.countDigitOne(lows)
14-
)
3+
@cache
4+
def dfs(pos, cnt, limit):
5+
if pos < 0:
6+
return cnt
7+
up = a[pos] if limit else 9
8+
ans = 0
9+
for i in range(up + 1):
10+
ans += dfs(pos - 1, cnt + (i == 1), limit and i == up)
11+
return ans
12+
13+
a = []
14+
while n:
15+
a.append(n % 10)
16+
n //= 10
17+
return dfs(len(a) - 1, 0, True)

0 commit comments

Comments
 (0)