82
82
83
83
为了避免重复计算,我们使用记忆化搜索。
84
84
85
- 时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为字符串 $s$ 的长度。
85
+ 时间复杂度 $O(n^2)$,空间复杂度 $O(n \times |\Sigma| )$。其中 $n$ 为字符串 $s$ 的长度,而 $|\Sigma|$ 表示字符集的大小,本题中 $|\Sigma| = 26$ 。
86
86
87
87
<!-- tabs:start -->
88
88
@@ -165,7 +165,7 @@ public:
165
165
int n = s.size();
166
166
int f[ n] ;
167
167
memset(f, -1, sizeof(f));
168
- function<int(int)> dfs = [ &] (int i) {
168
+ auto dfs = [ &] (auto&& dfs, int i) -> int {
169
169
if (i >= n) {
170
170
return 0;
171
171
}
@@ -186,12 +186,12 @@ public:
186
186
++cnt[ k] ;
187
187
++freq[ cnt[ k]] ;
188
188
if (freq.size() == 1) {
189
- f[ i] = min(f[ i] , 1 + dfs(j + 1));
189
+ f[ i] = min(f[ i] , 1 + dfs(dfs, j + 1));
190
190
}
191
191
}
192
192
return f[ i] ;
193
193
};
194
- return dfs(0);
194
+ return dfs(dfs, 0);
195
195
}
196
196
};
197
197
```
@@ -276,4 +276,320 @@ function minimumSubstringsInPartition(s: string): number {
276
276
277
277
<!-- solution: end -->
278
278
279
+ <!-- solution: start -->
280
+
281
+ ### 方法二:记忆化搜索(优化)
282
+
283
+ 我们可以对方法一进行优化,不需要维护 $\textit{freq}$ 哈希表,只需要维护一个哈希表 $\textit{cnt}$,表示当前子字符串中每个字符出现的次数。另外,维护两个变量 $k$ 和 $m$ 分别表示当前子字符串中的字符种类数和出现次数最多的字符的出现次数。对于一个子串 $s[ i..j] $,如果 $j-i+1 = m \times k$,那么这个子串就是一个平衡子串。
284
+
285
+ 时间复杂度 $O(n^2)$,空间复杂度 $O(n \times |\Sigma|)$。其中 $n$ 为字符串 $s$ 的长度,而 $|\Sigma|$ 表示字符集的大小,本题中 $|\Sigma| = 26$。
286
+
287
+ <!-- tabs: start -->
288
+
289
+ #### Python3
290
+
291
+ ``` python
292
+ class Solution :
293
+ def minimumSubstringsInPartition (self , s : str ) -> int :
294
+ @cache
295
+ def dfs (i : int ) -> int :
296
+ if i >= n:
297
+ return 0
298
+ cnt = defaultdict(int )
299
+ m = 0
300
+ ans = n - i
301
+ for j in range (i, n):
302
+ cnt[s[j]] += 1
303
+ m = max (m, cnt[s[j]])
304
+ if j - i + 1 == m * len (cnt):
305
+ ans = min (ans, 1 + dfs(j + 1 ))
306
+ return ans
307
+
308
+ n = len (s)
309
+ ans = dfs(0 )
310
+ dfs.cache_clear()
311
+ return ans
312
+ ```
313
+
314
+ #### Java
315
+
316
+ ``` java
317
+ class Solution {
318
+ private int n;
319
+ private char [] s;
320
+ private Integer [] f;
321
+
322
+ public int minimumSubstringsInPartition (String s ) {
323
+ n = s. length();
324
+ f = new Integer [n];
325
+ this . s = s. toCharArray();
326
+ return dfs(0 );
327
+ }
328
+
329
+ private int dfs (int i ) {
330
+ if (i >= n) {
331
+ return 0 ;
332
+ }
333
+ if (f[i] != null ) {
334
+ return f[i];
335
+ }
336
+ int [] cnt = new int [26 ];
337
+ int ans = n - i;
338
+ int k = 0 , m = 0 ;
339
+ for (int j = i; j < n; ++ j) {
340
+ k += ++ cnt[s[j] - ' a' ] == 1 ? 1 : 0 ;
341
+ m = Math . max(m, cnt[s[j] - ' a' ]);
342
+ if (j - i + 1 == k * m) {
343
+ ans = Math . min(ans, 1 + dfs(j + 1 ));
344
+ }
345
+ }
346
+ return f[i] = ans;
347
+ }
348
+ }
349
+ ```
350
+
351
+ #### C++
352
+
353
+ ``` cpp
354
+ class Solution {
355
+ public:
356
+ int minimumSubstringsInPartition(string s) {
357
+ int n = s.size();
358
+ int f[ n] ;
359
+ memset(f, -1, sizeof(f));
360
+ auto dfs = [ &] (auto&& dfs, int i) -> int {
361
+ if (i >= n) {
362
+ return 0;
363
+ }
364
+ if (f[ i] != -1) {
365
+ return f[ i] ;
366
+ }
367
+ f[ i] = n - i;
368
+ int cnt[ 26] {};
369
+ int k = 0, m = 0;
370
+ for (int j = i; j < n; ++j) {
371
+ k += ++cnt[ s[ j] - 'a'] == 1 ? 1 : 0;
372
+ m = max(m, cnt[ s[ j] - 'a'] );
373
+ if (j - i + 1 == k * m) {
374
+ f[ i] = min(f[ i] , 1 + dfs(dfs, j + 1));
375
+ }
376
+ }
377
+ return f[ i] ;
378
+ };
379
+ return dfs(dfs, 0);
380
+ }
381
+ };
382
+ ```
383
+
384
+ #### Go
385
+
386
+ ```go
387
+ func minimumSubstringsInPartition(s string) int {
388
+ n := len(s)
389
+ f := make([]int, n)
390
+ for i := range f {
391
+ f[i] = -1
392
+ }
393
+ var dfs func(int) int
394
+ dfs = func(i int) int {
395
+ if i >= n {
396
+ return 0
397
+ }
398
+ if f[i] != -1 {
399
+ return f[i]
400
+ }
401
+ cnt := [26]int{}
402
+ f[i] = n - i
403
+ k, m := 0, 0
404
+ for j := i; j < n; j++ {
405
+ x := int(s[j] - 'a')
406
+ cnt[x]++
407
+ if cnt[x] == 1 {
408
+ k++
409
+ }
410
+ m = max(m, cnt[x])
411
+ if j-i+1 == k*m {
412
+ f[i] = min(f[i], 1+dfs(j+1))
413
+ }
414
+ }
415
+ return f[i]
416
+ }
417
+ return dfs(0)
418
+ }
419
+ ```
420
+
421
+ #### TypeScript
422
+
423
+ ``` ts
424
+ function minimumSubstringsInPartition(s : string ): number {
425
+ const n = s .length ;
426
+ const f: number [] = Array (n ).fill (- 1 );
427
+ const dfs = (i : number ): number => {
428
+ if (i >= n ) {
429
+ return 0 ;
430
+ }
431
+ if (f [i ] !== - 1 ) {
432
+ return f [i ];
433
+ }
434
+ const cnt: number [] = Array (26 ).fill (0 );
435
+ f [i ] = n - i ;
436
+ let [k, m] = [0 , 0 ];
437
+ for (let j = i ; j < n ; ++ j ) {
438
+ const x = s .charCodeAt (j ) - 97 ;
439
+ k += ++ cnt [x ] === 1 ? 1 : 0 ;
440
+ m = Math .max (m , cnt [x ]);
441
+ if (j - i + 1 === k * m ) {
442
+ f [i ] = Math .min (f [i ], 1 + dfs (j + 1 ));
443
+ }
444
+ }
445
+ return f [i ];
446
+ };
447
+ return dfs (0 );
448
+ }
449
+ ```
450
+
451
+ <!-- tabs: end -->
452
+
453
+ <!-- solution: end -->
454
+
455
+ <!-- solution: start -->
456
+
457
+ ### 方法三:动态规划
458
+
459
+ 我们可以将记忆化搜索转换为动态规划,定义状态 $f[ i] $ 对前 $i$ 个字符进行分割的最少子字符串数量。初始时 $f[ 0] = 0$,其余 $f[ i] = +\infty$ 或者 $f[ i] = n$。
460
+
461
+ 接下来我们枚举 $i$ 从 $0$ 到 $n-1$,对于每个 $i$,我们维护一个哈希表 $\textit{cnt}$,表示当前子字符串中每个字符出现的次数。另外,我们维护两个变量 $k$ 和 $m$ 分别表示当前子字符串中的字符种类数和出现次数最多的字符的出现次数。对于一个子串 $s[ j..i] $,如果 $i-j+1 = m \times k$,那么这个子串就是一个平衡子串。此时我们可以从 $j$ 开始分割,那么 $f[ i+1] = \min(f[ i+1] , f[ j] + 1)$。
462
+
463
+ 最终答案为 $f[ n] $。
464
+
465
+ 时间复杂度 $O(n^2)$,空间复杂度 $O(n + |\Sigma|)$。其中 $n$ 为字符串 $s$ 的长度,而 $|\Sigma|$ 表示字符集的大小,本题中 $|\Sigma| = 26$。
466
+
467
+ <!-- tabs: start -->
468
+
469
+ #### Python3
470
+
471
+ ``` python
472
+ class Solution :
473
+ def minimumSubstringsInPartition (self , s : str ) -> int :
474
+ n = len (s)
475
+ f = [inf] * (n + 1 )
476
+ f[0 ] = 0
477
+ for i in range (n):
478
+ cnt = defaultdict(int )
479
+ m = 0
480
+ for j in range (i, - 1 , - 1 ):
481
+ cnt[s[j]] += 1
482
+ m = max (m, cnt[s[j]])
483
+ if i - j + 1 == len (cnt) * m:
484
+ f[i + 1 ] = min (f[i + 1 ], f[j] + 1 )
485
+ return f[n]
486
+ ```
487
+
488
+ #### Java
489
+
490
+ ``` java
491
+ class Solution {
492
+ public int minimumSubstringsInPartition (String s ) {
493
+ int n = s. length();
494
+ char [] cs = s. toCharArray();
495
+ int [] f = new int [n + 1 ];
496
+ Arrays . fill(f, n);
497
+ f[0 ] = 0 ;
498
+ for (int i = 0 ; i < n; ++ i) {
499
+ int [] cnt = new int [26 ];
500
+ int k = 0 , m = 0 ;
501
+ for (int j = i; j >= 0 ; -- j) {
502
+ k += ++ cnt[cs[j] - ' a' ] == 1 ? 1 : 0 ;
503
+ m = Math . max(m, cnt[cs[j] - ' a' ]);
504
+ if (i - j + 1 == k * m) {
505
+ f[i + 1 ] = Math . min(f[i + 1 ], 1 + f[j]);
506
+ }
507
+ }
508
+ }
509
+ return f[n];
510
+ }
511
+ }
512
+ ```
513
+
514
+ #### C++
515
+
516
+ ``` cpp
517
+ class Solution {
518
+ public:
519
+ int minimumSubstringsInPartition(string s) {
520
+ int n = s.size();
521
+ vector<int > f(n + 1, n);
522
+ f[ 0] = 0;
523
+ for (int i = 0; i < n; ++i) {
524
+ int cnt[ 26] {};
525
+ int k = 0, m = 0;
526
+ for (int j = i; ~ j; --j) {
527
+ k += ++cnt[ s[ j] - 'a'] == 1;
528
+ m = max(m, cnt[ s[ j] - 'a'] );
529
+ if (i - j + 1 == k * m) {
530
+ f[ i + 1] = min(f[ i + 1] , f[ j] + 1);
531
+ }
532
+ }
533
+ }
534
+ return f[ n] ;
535
+ }
536
+ };
537
+ ```
538
+
539
+ #### Go
540
+
541
+ ```go
542
+ func minimumSubstringsInPartition(s string) int {
543
+ n := len(s)
544
+ f := make([]int, n+1)
545
+ for i := range f {
546
+ f[i] = n
547
+ }
548
+ f[0] = 0
549
+ for i := 0; i < n; i++ {
550
+ cnt := [26]int{}
551
+ k, m := 0, 0
552
+ for j := i; j >= 0; j-- {
553
+ x := int(s[j] - 'a')
554
+ cnt[x]++
555
+ if cnt[x] == 1 {
556
+ k++
557
+ }
558
+ m = max(m, cnt[x])
559
+ if i-j+1 == k*m {
560
+ f[i+1] = min(f[i+1], 1+f[j])
561
+ }
562
+ }
563
+ }
564
+ return f[n]
565
+ }
566
+ ```
567
+
568
+ #### TypeScript
569
+
570
+ ``` ts
571
+ function minimumSubstringsInPartition(s : string ): number {
572
+ const n = s .length ;
573
+ const f: number [] = Array (n + 1 ).fill (n );
574
+ f [0 ] = 0 ;
575
+ for (let i = 0 ; i < n ; ++ i ) {
576
+ const cnt: number [] = Array (26 ).fill (0 );
577
+ let [k, m] = [0 , 0 ];
578
+ for (let j = i ; ~ j ; -- j ) {
579
+ const x = s .charCodeAt (j ) - 97 ;
580
+ k += ++ cnt [x ] === 1 ? 1 : 0 ;
581
+ m = Math .max (m , cnt [x ]);
582
+ if (i - j + 1 === k * m ) {
583
+ f [i + 1 ] = Math .min (f [i + 1 ], 1 + f [j ]);
584
+ }
585
+ }
586
+ }
587
+ return f [n ];
588
+ }
589
+ ```
590
+
591
+ <!-- tabs: end -->
592
+
593
+ <!-- solution: end -->
594
+
279
595
<!-- problem: end -->
0 commit comments