【数据结构与算法】之深入解析“最长有效括号”的求解思路与算法示例

举报
Serendipity·y 发表于 2022/02/17 00:41:45 2022/02/17
【摘要】 一、题目要求 给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。示例 1: 输入:s = "(()" 输出:2 解释:最长有效括号子串是 "()" 123 ...

一、题目要求

  • 给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
  • 示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

  
 
  • 1
  • 2
  • 3
  • 示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

  
 
  • 1
  • 2
  • 3
  • 示例 3:
输入:s = ""
输出:0

  
 
  • 1
  • 2
  • 提示:
    • 0 <= s.length <= 3 * 104;
    • s[i] 为 ‘(’ 或 ‘)’。

二、求解算法

① 动态规划

  • 结合题目,有“最长”要求,可以考虑尝试使用“动态规划”进行分析,这是一个“最值型”的动态规划题目。
  • 动态规划题目分析的 4 个步骤:
    • 确定状态:研究最优策略的最后一步,化为子问题;
    • 转移方程:根据子问题定义得到;
    • 初始条件和边界情况;
    • 计算顺序。
  • 首先,定义一个 dp 数组,其中第 i 个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。
  • 确定状态的最后一步,对于最优的策略,一定有最后一个元素 s[i],因此先看第 i 个位置,这个位置的元素 s[i] 可能有如下两种情况:
    • s[i]== ′(′,s[i] 无法和其之前的元素组成有效的括号对,所以 dp[i]=0;
    • s[i]== ′)′,需要看其前面对元素来判断是否有有效括号对。
  • s[i-1]== ′(′,即 s[i] 和 s[i−1] 组成一对有效括号,有效括号长度新增长度 2,i 位置对最长有效括号长度为其之前 2 个位置的最长括号长度加上当前位置新增的 2,无需知道 i−2 位置对字符是否可以组成有效括号对。那么有:dp[i]=dp[i−2]+2;

在这里插入图片描述

  • s[i−1] == ′)′,这种情况下,如果前面有和 s[i] 组成有效括号对的字符,即形如 ((…)),这样的话,就要求 s[i−1] 位置必然是有效的括号对,否则 s[i] 无法和前面对字符组成有效括号对。这时,只需要找到和 s[i] 配对对位置,并判断其是否是 ( 即可。和其配对对位置为:i−dp[i−1]−1。
  • 如果 s[i−dp[i−1]−1]== ′(′,有效括号长度新增长度 2,i 位置对最长有效括号长度为 i-1 位置的最长括号长度加上当前位置新增的 2,那么有:dp[i]=dp[i−1]+2。值得注意的是,i−dp[i−1]−1 和 i 组成了有效括号对,这将是一段独立的有效括号序列,如果之前的子序列是形如 (…) 这种序列,那么当前位置的最长有效括号长度还需要加上这一段,所以 dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2。

在这里插入图片描述

  • 子问题:根据上面的分析,可以得到如下两个计算公式:
    • dp[i]=dp[i−2]+2
    • dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
  • 那么,求 dp[i] 就变成了求 dp[i−1]、 dp[i−2]、dp[i−dp[i−1]−2] 的子问题。这样状态也明确了:设 dp 数组,其中第 i 个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。
  • 转移方程:子问题明确后,转移方程直接由子问题得到:
if s[i] == '(' :
    dp[i] = 0
if s[i] == ')' :
    if s[i - 1] == '(' :
        dp[i] = dp[i - 2] + 2 #要保证i - 2 >= 0

    if s[i - 1] == ')' and s[i - dp[i - 1] - 1] == '(' :
        dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2 #要保证i - dp[i - 1] - 2 >= 0

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 初始条件和边界情况:
    • 初始条件: dp[i]=0;
    • 边界情况:需要保证计算过程中:i−2>=0 和 i−dp[i−1]−2>=0。
  • 计算顺序:
    • 无论第一个字符是什么,都有:dp[0]=0;
    • 然后依次计算:dp[1],dp[2],…,dp[n−1];
    • 结果是: max(dp[i])。
  • C++ 示例:
class Solution {
public:
    int longestValidParentheses(string s) {
        int size = s.length();
        vector<int> dp(size, 0);

        int maxVal = 0;
        for(int i = 1; i < size; i++) {
            if (s[i] == ')') {
                if (s[i - 1] == '(') {
                    dp[i] = 2;
                    if (i - 2 >= 0) {
                        dp[i] = dp[i] + dp[i - 2];
                    }
                } else if (dp[i - 1] > 0) {
                    if ((i - dp[i - 1] - 1) >= 0 && s[i - dp[i - 1] - 1] == '(') {
                        dp[i] = dp[i - 1] + 2;
                        if ((i - dp[i - 1] - 2) >= 0) {
                            dp[i] = dp[i] + dp[i - dp[i - 1] - 2];
                        }
                    }
                }
            }
            maxVal = max(maxVal, dp[i]);
        }
        return maxVal;
    }
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • Java 示例:
class Solution {
    public int longestValidParentheses(String s) {
        char[] chs = s.toCharArray();
        int[] dp = new int[chs.length];
        int res = 0;
        for (int i = 1; i < chs.length; i++) {
            if (chs[i] == ')') {
                if (chs[i - 1] == '(') dp[i] = 2 + ((i - 2 >= 0) ? dp[i - 2] : 0);
                else {
                    int x = i - dp[i-1] - 1 ;
                    boolean flag = x >= 0 && chs[x] == '(' && (dp[i] = dp[i-1] + 2 + (x >= 1 ? dp[x-1] : 0)) > 1;
                }
                res = Math.max(res, dp[i]);
            }
        }
        return res;

    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

② 栈

  • 撇开动态规划方法,相信大多数人对于这题的第一直觉是找到每个可能的子串后判断它的有效性,但这样的时间复杂度会达到 O(n3),无法通过所有测试用例。但是通过栈,可以在遍历给定字符串的过程中去判断到目前为止扫描的子串的有效性,同时能得到最长有效括号的长度。
  • 具体做法是始终保持栈底元素为当前已经遍历过的元素中「最后一个没有被匹配的右括号的下标」,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标:
    • 对于遇到的每个 ‘(’ ,将它的下标放入栈中;
    • 对于遇到的每个 ‘)’ ,先弹出栈顶元素表示匹配了当前右括号:如果栈为空,说明当前的右括号为没有被匹配的右括号,将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」;如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」。
  • 我们从前往后遍历字符串并更新答案即可。需要注意的是,如果一开始栈为空,第一个字符为左括号的时候将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」。
  • 为了保持统一,在一开始的时候往栈中放入一个值为 −1 的元素:

在这里插入图片描述

  • 首先 0 入栈:

在这里插入图片描述

  • 0 出栈:

在这里插入图片描述

  • -1 出栈:

在这里插入图片描述

  • 此时,栈为空:

在这里插入图片描述

  • 继续,2 入栈:

在这里插入图片描述

  • 3 入栈:

在这里插入图片描述

  • 4 入栈:

在这里插入图片描述

  • 5 入栈:

在这里插入图片描述

  • 5 出栈:

在这里插入图片描述

  • 再 4 出栈:

在这里插入图片描述

  • Java 示例:
class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.isEmpty()) {
                    stack.push(i);
                } else {
                    maxans = Math.max(maxans, i - stack.peek());
                }
            }
        }
        return maxans;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • C++ 示例:
class Solution {
public:
    int longestValidParentheses(string s) {
        int maxans = 0;
        stack<int> stk;
        stk.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == '(') {
                stk.push(i);
            } else {
                stk.pop();
                if (stk.empty()) {
                    stk.push(i);
                } else {
                    maxans = max(maxans, i - stk.top());
                }
            }
        }
        return maxans;
    }
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/122808738

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。