hi大家!终于,终于,今天迎来了动态规划小结的最后一题,我们今天用一道hard暂别动态规划
先来看题目
32. 最长有效括号
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 "(()())"。
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
题目分析
1.解题思路
要配对括号,找右括号来凑左括号,和找左括号来凑右括号,思路基本都是一致的,因为有效的括号就是左括号在前,右括号在后,正向遍历字符串,如果先遇到左括号,暂时没有右括号和它配对,那这个位置的有效括号长度就赋值为0,往后遍历,当遍历到右括号时,就会往前找还未被配对的左括号
- 如果前一个字符就是左括号,直接匹配(形成
())。 - 如果前一个字符是右括号,说明可能存在嵌套或连续的有效子串,需要跳过已匹配的范围,找更前面的左括号。
2.dp数组含义
我们写动态规划这类题,通常会借用dp数组,那这道题的dp数组的含义是什么呢,我们要分析到某个位置的有效括号的长度,因此 数组的索引 i 就对应字符串的索引,可以对应找到字符串中对应的元素,dp i 就表示以当前元素结尾的有效括号的长度
3.状态转移方程
前面分析解题思路我们知道,在遍历到右括号时,去前面找左括号需要分两种情况讨论
1.如果当前元素 i 的前一位就是左括号,那就恰好能匹配,匹配成的括号的有效长度是2,但是别忘了还要加上元素i-1前面那段字符串中括号的有效长度,就是到 i-2位置时,括号的有效长度
此时dp i = dp i-2 + 2
2.如果 i 的前一位 i - 1 仍然是右括号,那我们要找和 i 位置匹配的左括号,就应该跳过前面 i-1位置匹配过的有效括号长度,找到有效长度的前一个位置
int pre = dpi - dp\[i - 1 - 1]
判断这个位置是不是左括号,如果是就能匹配成功,如果这个位置还是右括号,说明 i 位置已经不可能匹配成功了,因为即使再往前还有左括号,因为中间还隔着有效长度的前一个位置上这个右括号
所以此时 dpi = dpi - 1 + pre + 2;
数组的初始化
因为一开始并不能知道每个位置上的匹配情况,所以dp数组先初始化为0,但因为数组本身默认值就为0,我们初始化无需进行更多操作
后面循环过程中更新有效值就可以了
好了,那题目分析到这里大家应该也能理解了,我们来看看代码是如何实现的
java
class Solution {
public int longestValidParentheses(String s) {
//定义dp数组:索引i和字符串的索引i对应,指向当前元素,dp[i]表示以当前元素结尾的最大有效括号长度
int[] dp = new int[s.length()];
//数组默认初始值是0,全都先默认是无效状态
//异常处理,如果字符串长度是0或者小于2,有效长度都是0,直接返回0
if (s.length() == 0 || s.length() < 2) {
return 0;
}
//仅有0索引位置元素是没办法找到有效括号的,至少都需要两个元素,所以索引直接从1开始
for (int i = 1; i < s.length(); i++) {
/* 不需要这个步骤,因为数组中所有的初始值本来就是0,我们只需要把有效部分的值更新就好了
if(s.charAt(i)=='('){
dp[i]=0;
}*/
//只考虑当前元素是 )的情况
if (s.charAt(i) == ')') {
//如果前一位刚好是 ( 能和当前元素组成有效的括号,但此时还要考虑前一位的前面有效括号的长度
//但是这里为了避免索引越界异常,需要进行边界判断,如果当前元素i小于2,那么说明前面有效括号的长度是0,直接加0
//如果大于等于2,则需要加上前面的有效括号长度
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
//如果前一位仍然是 )怎么办
} else if (s.charAt(i - 1) == ')') {
//需要跳过前一位的有效范围,因为前一位如果有匹配的 (,那这个范围内是不会再有(出现的,只能在这个有效范围的前面一位上找
//为什么是前一位不是前面的整个范围呢?
/*1.如果这个位置是左括号,说明能配对,形成更大的有效子串。
2. 如果这个位置是右括号,说明当前右括号无法配对(因为更左侧的位置即使有左括号,中间也隔着这个未配对的右括号,无法和 i 位置形成有效括号)*/
if (i - dp[i - 1] - 1 >= 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
//这里还是依然对边界进行了判断
//得到前面部分的有效长度
int pre = i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0;
//以i元素结尾的括号的有效长度=前面部分的有效长度+前一位的有效长度+2(i位置括号匹配,长度是2)
dp[i] = dp[i - 1] + pre + 2;
}
}
}
}
//最后获取dp数组中的最大值就是有效括号长度的最大值
int max = dp[0];
for (int i = 1; i < dp.length; i++) {
max = Math.max(max, dp[i]);
}
return max;
}
}
这题还有一点很重要,就是索引越界异常的判断,我代码里面标注的很清楚了
好了,那这道题就写到这里了,各位明天见!