合法括号序列的充要条件

  1. 任意前缀中左括号的数量大于等于右括号的数量
  2. 左右括号的个数相等

性质:

  1. 如果要求最长有效括号的个数,要记住每个最长有效括号串是各不相交的。

32. 最长有效括号

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”

示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”

示例 3:
输入:s = “”
输出:0


提示:

  • 0 <= s.length <= 3 * 104
  • s[i]'('')'

    1. //方法一:栈
    2. class Solution {
    3. public int longestValidParentheses(String s) {
    4. Deque<Integer> q = new LinkedList<>();
    5. //方便处理边界往栈内加个-1
    6. q.push(-1);
    7. int ans = 0;
    8. for (int i = 0; i < s.length(); i++) {
    9. char ch = s.charAt(i);
    10. if (ch == '(')
    11. q.push(i);
    12. else {
    13. q.pop();
    14. if (q.isEmpty())
    15. q.push(i);
    16. else
    17. ans = Math.max(ans, i - q.peek());
    18. }
    19. }
    20. return ans;
    21. }
    22. }
    1. 方法二:贪心,左右各遍历一轮
    2. class Solution {
    3. public int longestValidParentheses(String s) {
    4. int c1 = 0, c2 = 0, ans = 0;
    5. for (int i = 0; i < s.length(); i++) {
    6. char ch = s.charAt(i);
    7. if (ch == '(')
    8. c1++;
    9. else {
    10. c2++;
    11. if (c2 == c1)
    12. ans = Math.max(ans, 2 * c2);
    13. else if (c2 > c1)
    14. c2 = c1 = 0;
    15. }
    16. }
    17. c1 = c2 = 0;
    18. for (int i = s.length() - 1; i >= 0; i--) {
    19. char ch = s.charAt(i);
    20. if (ch == ')')
    21. c1++;
    22. else {
    23. c2++;
    24. if (c2 == c1)
    25. ans = Math.max(ans, 2 * c2);
    26. else if (c2 > c1)
    27. c2 = c1 = 0;
    28. }
    29. }
    30. return ans;
    31. }
    32. }

    如果只从左往右遍历,"()(()()" 这种情况不能被正确处理,因为为无论什么时候 c1 >= c2 都成立,但是ans只能变为2,而不是4,因为中间多了一个 ( ,相反,从右往左遍历一次就能很好处理这种情况! 类似的 "(()))()" 从右往左不能正确处理但是从左往右就可以

Acwing 4198. 最长合法括号子串

  1. import java.util.*;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner sc = new Scanner(System.in);
  5. String s = sc.next();
  6. int n = s.length();
  7. int[] stk = new int[n + 1];
  8. stk[0] = -1;
  9. int p = 0;
  10. int max = 0, cnt = 0;
  11. for (int i = 0; i < n; i++) {
  12. char c = s.charAt(i);
  13. if (c == '(') {
  14. stk[++p] = i;
  15. } else {
  16. int l = stk[p--];
  17. if (p < 0 || s.charAt(l) == ')'){
  18. stk[++p] = l;
  19. stk[++p] = i;
  20. } else {
  21. int len = i - stk[p];
  22. if (max < len) {
  23. max = len;
  24. cnt = 1;
  25. } else if (max == len) {
  26. cnt++;
  27. }
  28. }
  29. }
  30. }
  31. if (max != 0)
  32. System.out.println(max + " " + cnt);
  33. else
  34. System.out.println(0 + " " + 1);
  35. }
  36. }

921. 使括号有效的最少添加

给定一个由 '('')' 括号组成的字符串 S,我们需要添加最少的括号( '(' 或是 ')',可以在任何位置),以使得到的括号字符串有效。
从形式上讲,只有满足下面几点之一,括号字符串才是有效的:

  • 它是一个空字符串,或者
  • 它可以被写成 ABAB 连接), 其中 AB 都是有效字符串,或者
  • 它可以被写作 (A),其中 A 是有效字符串。

给定一个括号字符串,返回为使结果字符串有效而必须添加的最少括号数。

示例 1:
输入:“())”
输出:1

示例 2:
输入:“(((“
输出:3

示例 3:
输入:“()”
输出:0

示例 4:
输入:“()))((“
输出:4

提示:

  1. S.length <= 1000
  2. S 只包含 '('')' 字符。

思路:贪心,能选就选
题目转换,添加最少字符使整个字符串合法等价于删除最少字符使字符串合理

  1. //遍历一遍,找出多余的左括号和右括号的数量就是应该删除的最小字符个数
  2. class Solution {
  3. public int minAddToMakeValid(String s) {
  4. int c1 = 0, c2 = 0;
  5. for (int i = 0; i < s.length(); i++) {
  6. if (s.charAt(i) == '(')
  7. c1++;
  8. else {
  9. if (c1 > 0)
  10. c1--;
  11. else
  12. c2++;
  13. }
  14. }
  15. return c1 + c2;
  16. }
  17. }

本题与32不同的是32题是找最长合法子串的长度,而本题相当于找最长合法子序列的长度,注意区别!!

Acwing 4207. 最长合法括号子序列

301. 删除无效的括号

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。
返回所有可能的结果。答案可以按 任意顺序 返回。

示例 1:
输入:s = “()())()”
输出:[“(())()”,”()()()”]

示例 2:
输入:s = “(a)())()”
输出:[“(a())()”,”(a)()()”]

示例 3:
输入:s = “)(“
输出:[“”]


提示:

  • 1 <= s.length <= 25
  • s 由小写英文字母以及括号 '('')' 组成
  • s 中至多含 20 个括号

思路:
921的进阶,不仅要找出最长合法子序列的长度,还要输出所有合法的最长子序列!
用 dfs + 剪枝
dfs时要注意保证当前保存的子串是合法的,即左括号数要大于右括号数
剪枝策略,对于一串连续的左括号或者右括号进行集中处理而不是一个一个的选择删或不删!

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. StringBuilder sb = new StringBuilder();
  4. public List<String> removeInvalidParentheses(String s) {
  5. int n = s.length(), c1 = 0, c2 = 0;
  6. for (int i = 0; i < n; i++) {
  7. char ch = s.charAt(i);
  8. if (ch == '(')
  9. c1++;
  10. else if (ch == ')')
  11. if (c1 > 0)
  12. c1--;
  13. else
  14. c2++;
  15. }
  16. dfs(0, s, 0, c1, c2);
  17. return res;
  18. }
  19. void dfs(int u, String s, int cnt, int c1, int c2) {
  20. if (u == s.length()) {
  21. if (cnt == 0)
  22. res.add(sb.toString());
  23. return;
  24. }
  25. char ch = s.charAt(u);
  26. if (ch != '(' && ch != ')') {
  27. sb.append(ch);
  28. dfs(u + 1, s, cnt, c1, c2);
  29. sb.deleteCharAt(sb.length() - 1);
  30. } else if (ch == '(') {
  31. int k = u;
  32. while (k < s.length() && s.charAt(k) == '(')
  33. k++;
  34. int len = k - u;
  35. c1 -= len;
  36. for (int i = len; i >= 0; i--) {
  37. if (c1 >= 0) {
  38. dfs(k, s, cnt, c1, c2);
  39. }
  40. sb.append('(');
  41. cnt++;
  42. c1++;
  43. }
  44. for (int i = len; i >= 0; i--) {
  45. sb.deleteCharAt(sb.length() - 1);
  46. }
  47. } else {
  48. int k = u;
  49. while (k < s.length() && s.charAt(k) == ')')
  50. k++;
  51. int len = k - u;
  52. c2 -= len;
  53. for (int i = len; i >= 0; i--) {
  54. if (cnt >= 0 && c2 >= 0)
  55. dfs(k, s, cnt, c1, c2);
  56. sb.append(')');
  57. cnt--;
  58. c2++;
  59. }
  60. for (int i = len; i >= 0; i--) {
  61. sb.deleteCharAt(sb.length() - 1);
  62. }
  63. }
  64. }
  65. }

678. 有效的括号字符串

给定一个只包含三种字符的字符串:*,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:

  1. 任何左括号 ( 必须有相应的右括号 )
  2. 任何右括号 ) 必须有相应的左括号 (
  3. 左括号 ( 必须在对应的右括号之前 )
  4. * 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
  5. 一个空字符串也被视为有效字符串。

示例 1:
输入: “()”
输出: True

示例 2:
输入: “()”
*输出:
True

示例 3:
输入: “())”
*输出:
True

注意:

  1. 字符串大小将在 [1,100] 范围内。

思路:
方法一:使用两个栈,一个存储*,另一个存储(
方法二:从左往右遍历一遍再从右往左遍历一遍,因为如果是合法字符串,无非三种情况

  1. 左右括号匹配,*号数量无所谓,从右往左和从左往右都能执行
  2. 左括号数量多于右括号,但*号可以填充部分右括号使其有效
  3. 右括号数量多于左括号,但*号可以填充部分左括号使其有效

    无效是不是一定能返回false?
    答:是的,从左到右是将全部看作左括号,能成功遍历的条件是每个右括号都有一个左括号或者与之对应
    从右到左是将全部看作右括号,能成功遍历的条件是每个左括号都有一个右括号或者与之对应

    1. //方法一:
    2. class Solution {
    3. public boolean checkValidString(String s) {
    4. Deque<Integer> stack = new LinkedList<>();
    5. Deque<Integer> star = new LinkedList<>();
    6. for (int i = 0; i < s.length(); i++) {
    7. char ch = s.charAt(i);
    8. if (ch == '(')
    9. stack.offer(i);
    10. else if (ch == '*')
    11. star.offer(i);
    12. else {
    13. if (!stack.isEmpty())
    14. stack.pollLast();
    15. else if(!star.isEmpty())
    16. star.pollLast();
    17. else
    18. return false;
    19. }
    20. }
    21. if (stack.isEmpty())
    22. return true;
    23. else {
    24. while (!stack.isEmpty()) {
    25. if (star.isEmpty() || star.peekLast() < stack.peekLast())
    26. return false;
    27. stack.pollLast();
    28. star.pollLast();
    29. }
    30. return true;
    31. }
    32. }
    33. }
    1. //方法二:左右各遍历一遍
    2. class Solution {
    3. public boolean checkValidString(String s) {
    4. int l = 0, r = 0, star = 0;
    5. for (int i = 0; i < s.length(); i++) {
    6. char ch = s.charAt(i);
    7. if (ch == '(')
    8. l++;
    9. else if (ch == '*')
    10. star++;
    11. else {
    12. if (l > 0)
    13. l--;
    14. else if (star > 0)
    15. star--;
    16. else
    17. return false;
    18. }
    19. }
    20. l = r = star = 0;
    21. for (int i = s.length() - 1; i >= 0; i--) {
    22. char ch = s.charAt(i);
    23. if (ch == ')')
    24. r++;
    25. else if (ch == '*')
    26. star++;
    27. else {
    28. if (r > 0)
    29. r--;
    30. else if (star > 0)
    31. star--;
    32. else
    33. return false;
    34. }
    35. }
    36. return true;
    37. }
    38. }

    5948. 判断一个括号字符串是否有效

    思路:678题换了种问法!

AcWing 1070. 括号配对

括号配对输出具体方案

Acwing 2004. 错字

问题描述:
一个括号串可能存在某位字符发生变化,即)变为(,或者是)变为(
请找到所有可能出错的位置,输出总个数

思路:
如果符号串本身合法,输出0即可
否则说明存在某位发生变化,先找出是做左括号多还是右括号多,然后遍历字符串找到所有能修改的位置
以左括号多为例:
说明需要将某个左括号改为右括号
倒着遍历,用一个计数器cc记录右括号 - 左括号的数量,若当前位置为(cc >= 0说明该左括号可以变为右括号,若当前位置cc < 0说明后面位置的左括号即使变为右括号也不能合法配对
如果是右括号更多,对称求解即可