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