14
14
15
15
我们可以在倒序枚举 $j$ 的同时,用一个哈希表(或者数组)统计每个字符的出现次数。如果子串中每个字母的出现次数都相等,那么子串是平衡的。
16
16
17
- ** 优化** :设子串中有 $k$ 种字母,如果子串长度不是 $k$ 的倍数,那么子串一定不是平衡的 。
17
+ ** 优化** :设子串中有 $k$ 种字母,字母出现次数的最大值为 $\textit{maxCnt}$。子串是平衡的,当且仅当子串长度等于 $k\cdot \textit{maxCnt}$ 。
18
18
19
19
递归边界:$\textit{dfs}(-1) = 0$。
20
20
@@ -38,17 +38,39 @@ class Solution:
38
38
return 0
39
39
res = inf
40
40
cnt = Counter()
41
+ max_cnt = 0
41
42
for j in range (i, - 1 , - 1 ):
42
43
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:
47
46
res = min (res, dfs(j - 1 ) + 1 )
48
47
return res
49
48
return dfs(len (s) - 1 )
50
49
```
51
50
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
+
52
74
``` java [sol-Java]
53
75
public class Solution {
54
76
public int minimumSubstringsInPartition (String S ) {
@@ -68,19 +90,13 @@ public class Solution {
68
90
}
69
91
int res = Integer . MAX_VALUE ;
70
92
int [] cnt = new int [26 ];
71
- int k = 0 ;
72
- next:
93
+ int k = 0 , maxCnt = 0 ;
73
94
for (int j = i; j >= 0 ; j-- ) {
74
95
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 );
82
99
}
83
- res = Math . min(res, dfs(j - 1 , s, memo) + 1 );
84
100
}
85
101
memo[i] = res; // 记忆化
86
102
return res;
@@ -103,19 +119,13 @@ public:
103
119
return res;
104
120
}
105
121
res = INT_MAX;
106
- int cnt[ 26] {}, k = 0;
122
+ int cnt[ 26] {}, k = 0, max_cnt = 0 ;
107
123
for (int j = i; j >= 0; j--) {
108
124
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);
111
128
}
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:;
119
129
}
120
130
return res;
121
131
};
@@ -142,23 +152,17 @@ func minimumSubstringsInPartition(s string) int {
142
152
}
143
153
res := math.MaxInt
144
154
cnt := [26]int{}
145
- k := 0
146
- next:
155
+ k, maxCnt := 0, 0
147
156
for j := i; j >= 0; j-- {
148
157
b := s[j] - 'a'
149
158
if cnt[b] == 0 {
150
159
k++
151
160
}
152
161
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)
160
165
}
161
- res = min(res, dfs(j-1)+1)
162
166
}
163
167
*p = res // 记忆化
164
168
return res
@@ -169,8 +173,8 @@ func minimumSubstringsInPartition(s string) int {
169
173
170
174
#### 复杂度分析
171
175
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}$ 数组。
174
178
175
179
## 方法二:递推(1:1 翻译)
176
180
@@ -193,16 +197,32 @@ class Solution:
193
197
f = [0 ] + [inf] * n
194
198
for i in range (n):
195
199
cnt = Counter()
200
+ max_cnt = 0
196
201
for j in range (i, - 1 , - 1 ):
197
202
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:
202
205
f[i + 1 ] = min (f[i + 1 ], f[j] + 1 )
203
206
return f[n]
204
207
```
205
208
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
+
206
226
``` java [sol-Java]
207
227
class Solution {
208
228
public int minimumSubstringsInPartition (String S ) {
@@ -214,19 +234,13 @@ class Solution {
214
234
int [] cnt = new int [26 ];
215
235
for (int i = 0 ; i < n; i++ ) {
216
236
Arrays . fill(cnt, 0 );
217
- int k = 0 ;
218
- next:
237
+ int k = 0 , maxCnt = 0 ;
219
238
for (int j = i; j >= 0 ; j-- ) {
220
239
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 );
228
243
}
229
- f[i + 1 ] = Math . min(f[i + 1 ], f[j] + 1 );
230
244
}
231
245
}
232
246
return f[n];
@@ -242,19 +256,13 @@ public:
242
256
vector<int > f(n + 1, INT_MAX);
243
257
f[ 0] = 0;
244
258
for (int i = 0; i < n; i++) {
245
- int cnt[ 26] {}, k = 0;
259
+ int cnt[ 26] {}, k = 0, max_cnt = 0 ;
246
260
for (int j = i; j >= 0; j--) {
247
261
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);
250
265
}
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:;
258
266
}
259
267
}
260
268
return f[ n] ;
@@ -269,23 +277,17 @@ func minimumSubstringsInPartition(s string) int {
269
277
for i := range s {
270
278
f[i+1] = math.MaxInt
271
279
cnt := [26]int{}
272
- k := 0
273
- next:
280
+ k, maxCnt := 0, 0
274
281
for j := i; j >= 0; j-- {
275
282
b := s[j] - 'a'
276
283
if cnt[b] == 0 {
277
284
k++
278
285
}
279
286
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)
287
290
}
288
- f[i+1] = min(f[i+1], f[j]+1)
289
291
}
290
292
}
291
293
return f[n]
@@ -294,8 +296,8 @@ func minimumSubstringsInPartition(s string) int {
294
296
295
297
#### 复杂度分析
296
298
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$。
299
301
300
302
## 分类题单
301
303
@@ -307,5 +309,6 @@ func minimumSubstringsInPartition(s string) int {
307
309
6 . [ 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)] ( https://leetcode.cn/circle/discuss/01LUak/ )
308
310
7 . [ 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)] ( https://leetcode.cn/circle/discuss/tXLS3i/ )
309
311
8 . [ 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)] ( https://leetcode.cn/circle/discuss/mOr1u6/ )
312
+ 9 . [ 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)] ( https://leetcode.cn/circle/discuss/IYT3ss/ )
310
313
311
314
[我的题解精选(已分类)](https:// github.com/EndlessCheng/codeforces-go/blob/master/leetcode/SOLUTIONS.md)
0 commit comments