Leetcode_32.最长有效括号

这篇博客介绍了如何使用动态规划解决LeetCode上的32题——最长有效括号问题。文章详细阐述了动态规划的解题思路,包括确定状态、转移方程、初始条件和边界情况,以及复杂度计算。此外,还提到了使用栈的另一种解题方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方法一:动态规划

解题思路:

结合题目,有最长这个字眼,可以考虑尝试使用动态规划进行分析。这是一个最值型动态规划的题目。
动态规划题目分析的 4 个步骤:

确定状态
    研究最优策略的最后一步
    化为子问题
转移方程
    根据子问题定义得到
初始条件和边界情况
计算顺序

首先,我们定义一个 dp[] 数组,其中第 i 个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。

确定状态:

最后一步:

对于最优的策略,一定有最后一个元素 s[i].
所以,我们先看第 i 个位置,这个位置的元素 s[i] 可能有如下两种情况:

  1. s[i] == ′(′ :此时,s[i] 无法和其之前的元素组成有效的括号对,所以,dp[i] = 0。

  2. s[i] == ′)′ :此时,需要看其前面的元素来判断是否形成有效括号对。
    情况1:
    s[i−1] == ′(′,即 s[i] 和 s[i−1] 组成一对有效括号,有效括号长度新增长度2,i 位置的最长有效括号长度为其前2个位置的最长括号长度加上当前位置新增的2,我们无需知道 i−2 位置的字符是否可以组成有效括号对。那么有:dp[i] = dp[i−2] + 2
    情况1情况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

初始条件和边界情况:

初始条件: 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])

复杂度计算:

时间复杂度: 遍历了一遍字符串,所以时间复杂度是:O(N)。
空间复杂度:需要和字符串长度相同的数组保存每个位置的最长有效括号长度,所以空间复杂度是:O(N)。

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;
    }
};
public class 最长有效括号 {

    public static int longestValidParentheses(String s) {
        int[] dp = new int[s.length()];
        int maxLen = 0;
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = 2;
                    if (i - 2 > 0) {
                        dp[i] += dp[i - 2];
                    }
                } else {
                    if (i - dp[i - 1] - 1 >= 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                        dp[i] = dp[i - 1] + 2;
                        if (i - dp[i - 1] - 2 > 0) {
                            dp[i] += dp[i - dp[i - 1] - 2];
                        }
                    }
                }
            }
            maxLen = maxLen > dp[i] ? maxLen : dp[i];
        }
        return maxLen;
    }

    public static void main(String[] args) {
        System.out.println(longestValidParentheses(")()())"));
        System.out.println(longestValidParentheses(")()(()))"));
    }
}

方法二:栈

解题思路:

关于括号匹配问题第一个想到的就是栈,初始化栈时将 -1 压入栈中,之后遍历字符串遇到左括号 ‘(’ 就将该括号对应的下标压入栈中,遇到右括号 ')'就弹出栈顶元素,用当前下标 i 减去(弹出之后)栈顶记录的下标值,就是当前的有效括号长度。

class Solution {
    public int longestValidParentheses(String s) {
        if (s == null || s.length() < 2) {
            return 0;
        }
        Stack<Integer> stack = new Stack();
        stack.push(-1);
        int maxLen = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.isEmpty()) {
                    stack.push(i);
                }
                maxLen = Math.max(maxLen, i - stack.peek());
            }
        }
        return maxLen;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值