@@ -490,6 +490,119 @@ class Solution {
490
490
```
491
491
** 所以剪枝确实很考验思维。我们也应该记住,修改(比如sort)源数据是个可以考虑的方案。**
492
492
493
+ ** 在括号相关的题目中,左括号要始终>= 右括号** ,否则字符串就不符合要求了。如果一个左括号score+1,一个右括号score-1,那么score要时刻保持大于0,用这一点剪枝非常有效。比如[删除无效的括号](https:// leetcode.cn/problems/remove-invalid-parentheses/description/):
494
+ ```java
495
+ class Solution {
496
+ public List<String > removeInvalidParentheses(String s) {
497
+ // remove Left, remove Right
498
+ int rl = 0 , rr = 0 , n = s. length();
499
+
500
+ int curL = 0 , curR = 0 , totalL = 0 , totalR = 0 ;
501
+ for (char c : s. toCharArray()) {
502
+ if (c == ' (' ) {
503
+ curL++ ;
504
+ totalL++ ;
505
+ }
506
+ if (c == ' )' ) {
507
+ curR++ ;
508
+ totalR++ ;
509
+ }
510
+
511
+ if (curR > curL) {
512
+ rr++ ;
513
+ curR-- ;
514
+ }
515
+ }
516
+
517
+ rl = curL - curR;
518
+
519
+ // print("rl", rl);print("rr", rr);
520
+
521
+ Set<String > result = new HashSet<> ();
522
+
523
+ dfs(result, " " , rl, rr, 0 , s, 0 );
524
+
525
+ return new ArrayList<> (result);
526
+ }
527
+
528
+ private void dfs(Set<String > result, String cur, int rl, int rr, int index, String raw, int score) {
529
+ // 剪枝:如果左右已经不对了,没必要继续下去
530
+ if (score < 0 ) {
531
+ return ;
532
+ }
533
+
534
+ // print("rl", rl);print("rr", rr);print("index", index);print("cur", cur);print("charAt", raw.charAt(index));
535
+ if (rl == 0 && rr == 0 ) {
536
+ if (isValid(cur + raw. substring(index))) {
537
+ result. add(cur + raw. substring(index));
538
+ return ;
539
+ }
540
+
541
+ // 在增长的过程中,不必check cur是否有效,还没增加完,很可能是无效的
542
+ // 在rl = rr = 0的时候做校验,这是一种剪枝
543
+ if (! isValid(cur)) {
544
+ return ;
545
+ }
546
+ }
547
+
548
+ // 这个不算剪枝,已经到头了,只能算最后的无效字符串过滤条件,不如换成下面这个剪枝1
549
+ if (index == raw. length()) {
550
+ return ;
551
+ }
552
+
553
+ // 剪枝1:如果剩余的字符不够删了,提前结束
554
+ if (rl + rr > raw. length() - index) {
555
+ return ;
556
+ }
557
+
558
+ if (rl > 0 && raw. charAt(index) == ' (' ) {
559
+ dfs(result, cur, rl - 1 , rr, index + 1 , raw, score);
560
+ }
561
+
562
+ if (rr > 0 && raw. charAt(index) == ' )' ) {
563
+ dfs(result, cur, rl, rr - 1 , index + 1 , raw, score);
564
+ }
565
+
566
+ if (raw. charAt(index) == ' (' ) {
567
+ score++ ;
568
+ } else if (raw. charAt(index) == ' )' ) {
569
+ score-- ;
570
+ }
571
+ dfs(result, cur + raw. charAt(index), rl, rr, index + 1 , raw, score);
572
+
573
+ // 恢复上下文
574
+ if (raw. charAt(index) == ' (' ) {
575
+ score-- ;
576
+ } else if (raw. charAt(index) == ' )' ) {
577
+ score++ ;
578
+ }
579
+ }
580
+
581
+ private boolean isValid(String s) {
582
+ int curL = 0 , curR = 0 ;
583
+ for (char c : s. toCharArray()) {
584
+ if (c == ' (' ) {
585
+ curL++ ;
586
+ }
587
+ if (c == ' )' ) {
588
+ curR++ ;
589
+ }
590
+
591
+ if (curR > curL) {
592
+ return false ;
593
+ }
594
+ }
595
+
596
+ return curL == curR;
597
+ }
598
+ }
599
+ ```
600
+ 不剪枝/ 加上剪枝1 / 再加上score剪枝,耗时分别为:522 / 36 / 13ms。
601
+
602
+ 但是注意这里score= 0 并不能作为最终字符串是否有效的标志,因为最后一段是直接用substring拼接上去的,没有计算score。** score只是标记了当前生成中的字符串是否是合法的** 。
603
+
604
+ 本题递归空间为2^n(相当于子集判断),生成字符串以后要再叠加isValid判断,所以是O (n * 2 ^n )。
605
+
493
606
## 题目类型:子集
494
607
单独[子集](https:// leetcode.cn/problems/subsets/description/)类型的题目拉出来,是因为发现时隔不久之后,**再写回溯的时候就“心中没树”了**!所以再拉出来把解题模板强化一遍。
495
608
@@ -857,3 +970,4 @@ class Solution {
857
970
- 返回值(返回方式)
858
971
859
972
** 通用dfs基本都是前序。如果是树,dfs的时候考虑一下要不要用后序(从下往上思考问题)。**
973
+
0 commit comments