Skip to content

Commit 7a7335a

Browse files
committed
优化
1 parent c87dcc8 commit 7a7335a

File tree

2 files changed

+87
-96
lines changed

2 files changed

+87
-96
lines changed

leetcode/biweekly/130/c/README.md

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $$
1414

1515
我们可以在倒序枚举 $j$ 的同时,用一个哈希表(或者数组)统计每个字符的出现次数。如果子串中每个字母的出现次数都相等,那么子串是平衡的。
1616

17-
**优化**:设子串中有 $k$ 种字母,如果子串长度不是 $k$ 的倍数,那么子串一定不是平衡的
17+
**优化**:设子串中有 $k$ 种字母,字母出现次数的最大值为 $\textit{maxCnt}$。子串是平衡的,当且仅当子串长度等于 $k\cdot \textit{maxCnt}$
1818

1919
递归边界:$\textit{dfs}(-1) = 0$。
2020

@@ -38,17 +38,39 @@ class Solution:
3838
return 0
3939
res = inf
4040
cnt = Counter()
41+
max_cnt = 0
4142
for j in range(i, -1, -1):
4243
cnt[s[j]] += 1
43-
if (i - j + 1) % len(cnt):
44-
continue
45-
c0 = cnt[s[j]]
46-
if all(c == c0 for c in cnt.values()):
44+
max_cnt = max(max_cnt, cnt[s[j]])
45+
if i - j + 1 == len(cnt) * max_cnt:
4746
res = min(res, dfs(j - 1) + 1)
4847
return res
4948
return dfs(len(s) - 1)
5049
```
5150

51+
```py [sol-Python3 写法二]
52+
class Solution:
53+
def minimumSubstringsInPartition(self, s: str) -> int:
54+
@cache
55+
def dfs(i: int) -> int:
56+
if i < 0:
57+
return 0
58+
res = inf
59+
cnt = Counter()
60+
max_cnt = 0
61+
for j in range(i, -1, -1):
62+
cnt[s[j]] += 1
63+
# 手动计算 max 和 min
64+
if cnt[s[j]] > max_cnt:
65+
max_cnt = cnt[s[j]]
66+
if i - j + 1 == len(cnt) * max_cnt:
67+
r = dfs(j - 1) + 1
68+
if r < res:
69+
res = r
70+
return res
71+
return dfs(len(s) - 1)
72+
```
73+
5274
```java [sol-Java]
5375
public class Solution {
5476
public int minimumSubstringsInPartition(String S) {
@@ -68,19 +90,13 @@ public class Solution {
6890
}
6991
int res = Integer.MAX_VALUE;
7092
int[] cnt = new int[26];
71-
int k = 0;
72-
next:
93+
int k = 0, maxCnt = 0;
7394
for (int j = i; j >= 0; j--) {
7495
k += cnt[s[j] - 'a']++ == 0 ? 1 : 0;
75-
if ((i - j + 1) % k != 0) {
76-
continue;
77-
}
78-
for (int c : cnt) {
79-
if (c > 0 && c != cnt[s[j] - 'a']) {
80-
continue next;
81-
}
96+
maxCnt = Math.max(maxCnt, cnt[s[j] - 'a']);
97+
if (i - j + 1 == k * maxCnt) {
98+
res = Math.min(res, dfs(j - 1, s, memo) + 1);
8299
}
83-
res = Math.min(res, dfs(j - 1, s, memo) + 1);
84100
}
85101
memo[i] = res; // 记忆化
86102
return res;
@@ -103,19 +119,13 @@ public:
103119
return res;
104120
}
105121
res = INT_MAX;
106-
int cnt[26]{}, k = 0;
122+
int cnt[26]{}, k = 0, max_cnt = 0;
107123
for (int j = i; j >= 0; j--) {
108124
k += cnt[s[j] - 'a']++ == 0;
109-
if ((i - j + 1) % k) {
110-
continue;
125+
max_cnt = max(max_cnt, cnt[s[j] - 'a']);
126+
if (i - j + 1 == k * max_cnt) {
127+
res = min(res, dfs(j - 1) + 1);
111128
}
112-
for (int c : cnt) {
113-
if (c && c != cnt[s[j] - 'a']) {
114-
goto next;
115-
}
116-
}
117-
res = min(res, dfs(j - 1) + 1);
118-
next:;
119129
}
120130
return res;
121131
};
@@ -142,23 +152,17 @@ func minimumSubstringsInPartition(s string) int {
142152
}
143153
res := math.MaxInt
144154
cnt := [26]int{}
145-
k := 0
146-
next:
155+
k, maxCnt := 0, 0
147156
for j := i; j >= 0; j-- {
148157
b := s[j] - 'a'
149158
if cnt[b] == 0 {
150159
k++
151160
}
152161
cnt[b]++
153-
if (i-j+1)%k > 0 {
154-
continue
155-
}
156-
for _, c := range cnt {
157-
if c > 0 && c != cnt[b] {
158-
continue next
159-
}
162+
maxCnt = max(maxCnt, cnt[b])
163+
if i-j+1 == k*maxCnt {
164+
res = min(res, dfs(j-1)+1)
160165
}
161-
res = min(res, dfs(j-1)+1)
162166
}
163167
*p = res // 记忆化
164168
return res
@@ -169,8 +173,8 @@ func minimumSubstringsInPartition(s string) int {
169173

170174
#### 复杂度分析
171175

172-
- 时间复杂度:$\mathcal{O}(n^2|\Sigma|)$,其中 $n$ 为 $s$ 的长度,$|\Sigma|$ 为字符集合的大小,本题字符均为小写字母,所以 $|\Sigma|=26$。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(n)$,单个状态的计算时间为 $\mathcal{O}(n|\Sigma|)$,所以动态规划的时间复杂度为 $\mathcal{O}(n^2|\Sigma|)$。
173-
- 空间复杂度:$\mathcal{O}(n|\Sigma|)$。注意递归中至多会创建 $n$ 个长为 $|\Sigma|$ 的 $\textit{cnt}$ 数组。
176+
- 时间复杂度:$\mathcal{O}(n^2)$,其中 $n$ 为 $s$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(n)$,单个状态的计算时间为 $\mathcal{O}(n)$,所以动态规划的时间复杂度为 $\mathcal{O}(n^2)$。
177+
- 空间复杂度:$\mathcal{O}(n|\Sigma|)$。其中 $|\Sigma|$ 为字符集合的大小,本题字符均为小写字母,所以 $|\Sigma|=26$。注意递归中至多会创建 $n$ 个长为 $|\Sigma|$ 的 $\textit{cnt}$ 数组。
174178

175179
## 方法二:递推(1:1 翻译)
176180

@@ -193,16 +197,32 @@ class Solution:
193197
f = [0] + [inf] * n
194198
for i in range(n):
195199
cnt = Counter()
200+
max_cnt = 0
196201
for j in range(i, -1, -1):
197202
cnt[s[j]] += 1
198-
if (i - j + 1) % len(cnt):
199-
continue
200-
c0 = cnt[s[j]]
201-
if all(c == c0 for c in cnt.values()):
203+
max_cnt = max(max_cnt, cnt[s[j]])
204+
if i - j + 1 == len(cnt) * max_cnt:
202205
f[i + 1] = min(f[i + 1], f[j] + 1)
203206
return f[n]
204207
```
205208

209+
```py [sol-Python3 写法二]
210+
class Solution:
211+
def minimumSubstringsInPartition(self, s: str) -> int:
212+
n = len(s)
213+
f = [0] + [inf] * n
214+
for i in range(n):
215+
cnt = Counter()
216+
max_cnt = 0
217+
for j in range(i, -1, -1):
218+
cnt[s[j]] += 1
219+
if cnt[s[j]] > max_cnt:
220+
max_cnt = cnt[s[j]]
221+
if i - j + 1 == len(cnt) * max_cnt and f[j] + 1 < f[i + 1]:
222+
f[i + 1] = f[j] + 1
223+
return f[n]
224+
```
225+
206226
```java [sol-Java]
207227
class Solution {
208228
public int minimumSubstringsInPartition(String S) {
@@ -214,19 +234,13 @@ class Solution {
214234
int[] cnt = new int[26];
215235
for (int i = 0; i < n; i++) {
216236
Arrays.fill(cnt, 0);
217-
int k = 0;
218-
next:
237+
int k = 0, maxCnt = 0;
219238
for (int j = i; j >= 0; j--) {
220239
k += cnt[s[j] - 'a']++ == 0 ? 1 : 0;
221-
if ((i - j + 1) % k > 0) {
222-
continue;
223-
}
224-
for (int c : cnt) {
225-
if (c != 0 && c != cnt[s[j] - 'a']) {
226-
continue next;
227-
}
240+
maxCnt = Math.max(maxCnt, cnt[s[j] - 'a']);
241+
if (i - j + 1 == k * maxCnt) {
242+
f[i + 1] = Math.min(f[i + 1], f[j] + 1);
228243
}
229-
f[i + 1] = Math.min(f[i + 1], f[j] + 1);
230244
}
231245
}
232246
return f[n];
@@ -242,19 +256,13 @@ public:
242256
vector<int> f(n + 1, INT_MAX);
243257
f[0] = 0;
244258
for (int i = 0; i < n; i++) {
245-
int cnt[26]{}, k = 0;
259+
int cnt[26]{}, k = 0, max_cnt = 0;
246260
for (int j = i; j >= 0; j--) {
247261
k += cnt[s[j] - 'a']++ == 0;
248-
if ((i - j + 1) % k) {
249-
continue;
262+
max_cnt = max(max_cnt, cnt[s[j] - 'a']);
263+
if (i - j + 1 == k * max_cnt) {
264+
f[i + 1] = min(f[i + 1], f[j] + 1);
250265
}
251-
for (int c : cnt) {
252-
if (c && c != cnt[s[j] - 'a']) {
253-
goto next;
254-
}
255-
}
256-
f[i + 1] = min(f[i + 1], f[j] + 1);
257-
next:;
258266
}
259267
}
260268
return f[n];
@@ -269,23 +277,17 @@ func minimumSubstringsInPartition(s string) int {
269277
for i := range s {
270278
f[i+1] = math.MaxInt
271279
cnt := [26]int{}
272-
k := 0
273-
next:
280+
k, maxCnt := 0, 0
274281
for j := i; j >= 0; j-- {
275282
b := s[j] - 'a'
276283
if cnt[b] == 0 {
277284
k++
278285
}
279286
cnt[b]++
280-
if (i-j+1)%k > 0 {
281-
continue
282-
}
283-
for _, c := range cnt {
284-
if c != 0 && c != cnt[b] {
285-
continue next
286-
}
287+
maxCnt = max(maxCnt, cnt[b])
288+
if i-j+1 == k*maxCnt {
289+
f[i+1] = min(f[i+1], f[j]+1)
287290
}
288-
f[i+1] = min(f[i+1], f[j]+1)
289291
}
290292
}
291293
return f[n]
@@ -294,8 +296,8 @@ func minimumSubstringsInPartition(s string) int {
294296

295297
#### 复杂度分析
296298

297-
- 时间复杂度:$\mathcal{O}(n^2|\Sigma|)$,其中 $n$ 为 $s$ 的长度,$|\Sigma|$ 为字符集合的大小,本题字符均为小写字母,所以 $|\Sigma|=26$
298-
- 空间复杂度:$\mathcal{O}(n + |\Sigma|)$。
299+
- 时间复杂度:$\mathcal{O}(n^2)$,其中 $n$ 为 $s$ 的长度。
300+
- 空间复杂度:$\mathcal{O}(n + |\Sigma|)$。其中 $|\Sigma|$ 为字符集合的大小,本题字符均为小写字母,所以 $|\Sigma|=26$。
299301

300302
## 分类题单
301303

@@ -307,5 +309,6 @@ func minimumSubstringsInPartition(s string) int {
307309
6. [图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)](https://leetcode.cn/circle/discuss/01LUak/)
308310
7. [动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)](https://leetcode.cn/circle/discuss/tXLS3i/)
309311
8. [常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)](https://leetcode.cn/circle/discuss/mOr1u6/)
312+
9. [数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)](https://leetcode.cn/circle/discuss/IYT3ss/)
310313

311314
[我的题解精选(已分类)](https://github.com/EndlessCheng/codeforces-go/blob/master/leetcode/SOLUTIONS.md)

leetcode/biweekly/130/c/c.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package main
33
import "math"
44

55
// https://space.bilibili.com/206214
6-
func minimumSubstringsInPartition(s string) int {
6+
func minimumSubstringsInPartition2(s string) int {
77
n := len(s)
88
memo := make([]int, n)
99
for i := range memo {
@@ -20,54 +20,42 @@ func minimumSubstringsInPartition(s string) int {
2020
}
2121
res := math.MaxInt
2222
cnt := [26]int{}
23-
k := 0
24-
next:
23+
k, maxCnt := 0, 0
2524
for j := i; j >= 0; j-- {
2625
b := s[j] - 'a'
2726
if cnt[b] == 0 {
2827
k++
2928
}
3029
cnt[b]++
31-
if (i-j+1)%k > 0 {
32-
continue
30+
maxCnt = max(maxCnt, cnt[b])
31+
if i-j+1 == k*maxCnt {
32+
res = min(res, dfs(j-1)+1)
3333
}
34-
for _, c := range cnt {
35-
if c > 0 && c != cnt[b] {
36-
continue next
37-
}
38-
}
39-
res = min(res, dfs(j-1)+1)
4034
}
4135
*p = res // 记忆化
4236
return res
4337
}
4438
return dfs(n - 1)
4539
}
4640

47-
func minimumSubstringsInPartition2(s string) int {
41+
func minimumSubstringsInPartition(s string) int {
4842
n := len(s)
4943
f := make([]int, n+1)
5044
for i := range s {
5145
f[i+1] = math.MaxInt
5246
cnt := [26]int{}
53-
k := 0
54-
next:
47+
k, maxCnt := 0, 0
5548
for j := i; j >= 0; j-- {
5649
b := s[j] - 'a'
5750
if cnt[b] == 0 {
5851
k++
5952
}
6053
cnt[b]++
61-
if (i-j+1)%k > 0 {
62-
continue
63-
}
64-
for _, c := range cnt {
65-
if c != 0 && c != cnt[b] {
66-
continue next
67-
}
54+
maxCnt = max(maxCnt, cnt[b])
55+
if i-j+1 == k*maxCnt {
56+
f[i+1] = min(f[i+1], f[j]+1)
6857
}
69-
f[i+1] = min(f[i+1], f[j]+1)
7058
}
7159
}
7260
return f[n]
73-
}
61+
}

0 commit comments

Comments
 (0)