1. 今日语录
一遍不行,就做两遍。
2. 题目
最长有效括号 给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
3. 思路
const s = "()(())"
/**
* 解法1 动态规划
* 根据题意,其实很容易判断出这是一道动态规划类型的题目。
* 从第一个字符出发,每新增一个字符就可能改变 最长有效括号max 的值。
* 找到其中规律,得到状态转移方案就成了解题的关键。
*
* 1. 首先,易推出,只有增加右括号 ), max才会有新增的可能。
* 2. 第一种情况,易推出,当s[i] = ), s[i - 1] = ( 时, dp[i] = dp[i - 2] + 2
* 3. 第二种情况,稍微复杂了些。
* 以 ()(() 对应 s[0,4] 为例,此时,i = 5, s[i] = ')', dp[4] = 2
* 此时 s[i-1] = s[4] = ')',但是 s[ i - dp[i-1] - 1] = '(',
* 这就形成了 (()()()……) 这种类型组合的最外层,所以 dp[i] += dp[i-1] + 2
*
* 然而,这并不是足够的,因为 …… (()()()……) 的前半部分可能还存在括号匹配的情况,
* 但因为 这种外层括号 在右括号到来前 一直没有形成包裹,造成 统计最长有效括号max值 中断了一部分。
*
*/
const longestValidParentheses_0 = (s) => {
let dp = new Array(s.length).fill(0)
let max = 0;
for (let i = 1; i < s.length; i++) {
if (s[i] === ')') {
if (s[i - 1] === '(') {
if (i >= 2) {
dp[i] = dp[i - 2] + 2;
} else {
dp[i] = 2;
}
} else {
if (s[i - 1 - dp[i - 1]] === '(') {
if (i - dp[i - 1] - 2 >= 0) {
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];
} else {
dp[i] = dp[i - 1] + 2;
}
}
}
}
max = Math.max(max, dp[i])
}
return max;
};
/**
* 解法2 栈
* 对于括号匹配类型的题目,栈的入栈 = 识别到左括号,左括号inx入栈
* 栈的出栈 = 识别到右括号,完成一次匹配,栈顶的左括号inx出栈
*
* 遇到右括号,先默认出栈,
* 1. 如果栈为空,说明当前的右括号为没有被匹配的右括号,
* 我们将其下标放入栈中,即 最后一个没有被匹配的右括号的下标。
* 2. 如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」
*
* 需要注意的是,如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」,
* 为了保持统一,我们在一开始的时候往栈中放入一个值为 −1 的元素。
**/
var longestValidParentheses_1 = function (s) {
let max = 0;
let stack = [-1];
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i)
} else {
stack.pop()
if (stack.length) {
max = Math.max(i - stack[stack.length - 1], max)
} else {
stack.push(i)
}
}
}
return max
};
/**
* 解法3 两个计数器
* 采用两个计数器,分别统计左括号和右括号
* 从左往右,当两个计数器相等时,统计最长有效括号。当右括号多于左括号时,计数器清零。
* 但这种方法会漏掉,例如((),即左括号个数始终占优的情况。
*
* 为了解决这个问题,我们可以从右到左遍历,条件取反,
* 而这种方法会漏掉,例如()),即右括号个数始终占优的情况。
*
* 两者结合,即可答案。
*/
var longestValidParentheses_2 = function (s) {
let maxLen = 0;
let left = 0;
let right = 0;
for (let i = 0; i < s.length; i++) { //从左往右
if (s[i] == "(") { //遇见'(' left++
left++;
} else {
right++; //遇见')' right++
}
if (left == right) { //左右数量相同
maxLen = Math.max(maxLen, 2 * left); //更新最大长度
} else if (right > left) { //right大于left 重置left right 重新计数
left = right = 0;
}
}
left = right = 0;
for (let i = s.length - 1; i >= 0; i--) { //从右往左
if (s[i] == "(") {
left++;
} else {
right++;
}
if (left == right) {
maxLen = Math.max(maxLen, right * 2);
} else if (left > right) {
left = right = 0;
}
}
return maxLen;
};
(function () {
console.log(s, '最长有效括号(动态规划) = ', longestValidParentheses_0(s));
console.log(s, '最长有效括号(栈) = ', longestValidParentheses_1(s));
console.log(s, '最长有效括号(两个计数器) = ', longestValidParentheses_2(s));
})()
4. 关键字
动态规划、栈、括号匹配、两次遍历