ID:32.最长有效括号

84 阅读3分钟

考点:栈、动态规划

题目链接

这个题的难点在于有效子串的分隔条件

参考官方题解的思路:

var longestValidParentheses = function(s) {
    if(s.length === 0) return 0;
    /**
     * 核心思想:假如这个字符串中有效括号序列有多个,
     * 它们之间可能是
     * 1. 通过'('隔开(例如 '()(()()' ),
     * 2. 也可能是通过')'隔开(例如 '())())()' )。
     * 
     * 针对括号的题目很容易想到用栈,本题也不例外。
     * 先设定左括号下标入栈,碰到右括号时出栈,
     * 并记录右括号和栈顶元素下标的差值为最长长度,
     * 当然遇到右括号时需要一些额外处理,后面会讲到。
     * 
     * 入栈的左括号下标可以作为最长子序列的左端点,
     * 由此分析一下情况 1:此时左括号分隔开了两个有效字符串,
     * 当遍历第二个有效字符串的时候,栈中始终有未找到匹配右括号的左括号下标,
     * 即下标 2,所以它自然地分隔了两个有效字符串。
     * 
     * 再分析一下情况 2:此时右括号分隔开了两个有效字符串,
     * 注意遇到右括号弹栈后可能会遇到栈空的情况,说明此下标的右括号是多余的,
     * 因此最长长度必须重置。
     * 我们可以在这种情况下把此下标入栈,就达到了分隔有效字符串的功能
     * 同时为了避免遇到右括号时栈已经空了,预先向栈中压入一个元素,
     * 因为下标是从 0 开始的,所以就用 -1
     * 比如像这种情况:'())())()',
     * 0、1 入栈出栈,此时栈中剩下 -1。然后遇到下标 2 的右括号,
     * 弹栈后栈为空,于是下标 2 入栈。那么可能就会有疑惑:
     * 后续遇到右括号还会弹栈,2 被弹出去了怎么办呢?
     * 其实无所谓,因为如果后面的右括号都有匹配的左括号,那么 2 永远会在栈底,
     * 起到一个分隔作用;如果遇到非法,即没有匹配的左括号的右括号(下标为 5)
     * 那么栈底元素就会被更新为 5,此时意味着 2 与 5 之间是一个有效子串,
     * 同时它不能和前面或者后面的有效子串合并,因为它的前后都有不合法的右括号。
     * 
     * 如果右括号是合法的,可以进行一次最长有效子串的判定,更新最大值。
     * 
     * 最后分析一下起始条件:栈中压入 -1。这个操作很关键,
     * 首先它保证遇到右括号弹栈的时候栈不为空,
     * 其次如果最长有效子串下标从 0 开始,它能够方便计算长度。
     */
    const stack = [-1];
    let max = 0;
    for(let i = 0; i < s.length; i++) {
        if(s[i] === '(') stack.push(i);
        else {
            stack.pop();
            if(stack.length === 0)  stack.push(i);
            else max = Math.max(max, i - stack[stack.length - 1]);
        }
    }
    return max;
};

当然本题主要还是考察动态规划,但是这个动态规划太难想了,有机会去看题解吧