Longest Valid Parentheses
HardPrompt
Given a string containing just the characters '(' and ')', return the length of the longest valid (well-formed) parentheses substring.
Example 1:
Input: s = "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()".Example 2:
Input: s = ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()".Example 3:
Input: s = ""
Output: 0
Constraints:
0 <= s.length <= 3 * 104s[i]is'(', or')'.
Approaches
4 approaches with complexity analysis and trade-offs.
This approach involves checking every possible substring of the given string to see if it is a valid parentheses sequence. We keep track of the length of the longest valid substring found.
Algorithm
- Initialize
maxLengthto 0. - Iterate through the string with a start index
ifrom 0 ton-1. - Iterate with an end index
jfromi+1ton-1. - Extract the substring from
itoj. - Check if the substring is a valid parentheses sequence using a helper function.
- To check for validity, use a counter. Iterate through the substring, incrementing for
(and decrementing for). If the counter ever becomes negative or is not zero at the end, it's invalid. - If the substring is valid, update
maxLength = max(maxLength, j - i + 1). - Return
maxLengthafter checking all substrings.
Walkthrough
We can generate every possible substring by using two nested loops. The outer loop fixes the starting point i and the inner loop fixes the ending point j of the substring.
For each substring, we need a helper function isValid(substring) to check if it's a well-formed parentheses string. This can be done using a counter. We iterate through the substring, incrementing the counter for ( and decrementing for ). If the counter ever drops below zero or is not zero at the end, the substring is invalid.
The overall time complexity is high because for each of the O(n^2) substrings, we perform a check that takes up to O(n) time.
class Solution { public boolean isValid(String s) { int balance = 0; for (char c : s.toCharArray()) { if (c == '(') { balance++; } else { balance--; } if (balance < 0) { return false; } } return balance == 0; } public int longestValidParentheses(String s) { int maxLen = 0; for (int i = 0; i < s.length(); i++) { for (int j = i + 1; j <= s.length(); j++) { if (isValid(s.substring(i, j))) { maxLen = Math.max(maxLen, j - i); } } } return maxLen; }}Complexity
Time
O(n^3)
Space
O(n)
Trade-offs
Pros
Simple to understand and implement the logic.
Cons
Extremely inefficient with a time complexity of O(n^3).
Will result in a 'Time Limit Exceeded' error on most platforms for medium to large inputs.
A more efficient approach using dynamic programming. We build a dp array where dp[i] stores the length of the longest valid parentheses substring that ends at index i.
Algorithm
- Create a
dparray of sizenand initialize it with zeros. - Initialize
maxLength = 0. - Iterate through the string
sfromi = 1ton-1. - If
s[i] == ')':- If
s[i-1] == '(', it forms a()pair. Setdp[i] = (i >= 2 ? dp[i-2] : 0) + 2. - Else if
s[i-1] == ')'and there's a matching(ati - dp[i-1] - 1, it forms a(...)structure. Setdp[i] = dp[i-1] + (i - dp[i-1] >= 2 ? dp[i - dp[i-1] - 2] : 0) + 2.
- If
- Update
maxLength = max(maxLength, dp[i])in each iteration. - Return
maxLength.
Walkthrough
The core idea is to build up the solution by reusing previous results. We create a dp array of the same size as the input string s.
dp[i] will be non-zero only if s[i] is ). A valid substring cannot end with (.
If s[i] is ):
- Case 1:
s[i-1]is(. This forms a()pair. The length is 2 plus the length of the valid substring ending ati-2. So,dp[i] = dp[i-2] + 2. - Case 2:
s[i-1]is). This forms a...))structure. If the substring ending ati-1is valid with lengthdp[i-1], we check the character before it, at indexi - dp[i-1] - 1. If that character is(, we have found a larger valid substring. Its length isdp[i-1] + 2plus the length of any valid substring ending just before this new one (ati - dp[i-1] - 2). So,dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2].
The maximum value in the dp array is the answer.
class Solution { public int longestValidParentheses(String s) { int maxans = 0; int[] dp = new int[s.length()]; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == ')') { if (s.charAt(i - 1) == '(') { dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') { dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2; } maxans = Math.max(maxans, dp[i]); } } return maxans; }}Complexity
Time
O(n)
Space
O(n)
Trade-offs
Pros
Efficient with linear time complexity.
Solves the problem for large inputs within time limits.
Cons
Requires O(n) extra space for the DP array.
The state transition logic can be complex to formulate correctly.
This approach uses a stack to keep track of the indices of parentheses. When a closing parenthesis is encountered, we can determine the length of the valid substring ending at the current index by using the indices stored in the stack.
Algorithm
- Initialize a stack and push
-1onto it. This value acts as a sentinel. - Initialize
maxLength = 0. - Iterate through the string
swith indexi. - If
s[i] == '(', push its indexionto the stack. - If
s[i] == ')':- Pop from the stack.
- If the stack is now empty, it means the current
)is unmatched. Push the current indexionto the stack to act as the new base for the next potential valid substring. - Else (stack is not empty), the current valid substring ends at
iand starts after the index at the top of the stack. Calculate its lengthlen = i - stack.peek()and updatemaxLength = max(maxLength, len).
- Return
maxLength.
Walkthrough
The stack is used to store the indices of ( characters. The key insight is to push a -1 onto the stack initially. This -1 acts as a sentinel value, representing the index just before a valid substring could start.
- When we see a
(at indexi, we pushionto the stack. - When we see a
)at indexi:- We pop from the stack. This pop represents finding a matching
(for the current). - If the stack becomes empty after popping, it means the current
)does not have a matching(. We then push the current indexionto the stack to serve as the new base for future valid substrings. - If the stack is not empty, the new top of the stack holds the index right before the start of the newly formed valid substring. The length of this substring is
current_index - stack.peek(). We update ourmaxLengthwith this value if it's larger.
- We pop from the stack. This pop represents finding a matching
import java.util.Stack; class Solution { public int longestValidParentheses(String s) { int maxans = 0; Stack<Integer> stack = new Stack<>(); stack.push(-1); for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { stack.push(i); } else { stack.pop(); if (stack.empty()) { stack.push(i); } else { maxans = Math.max(maxans, i - stack.peek()); } } } return maxans; }}Complexity
Time
O(n)
Space
O(n)
Trade-offs
Pros
Efficient with linear time complexity.
Often considered more intuitive than the dynamic programming approach.
Cons
Requires O(n) extra space for the stack in the worst case (e.g., a string of all open parentheses).
This is the most space-efficient approach. It uses two counters and scans the string twice: once from left to right, and once from right to left, to find the longest valid substring without using any extra data structures.
Algorithm
- Initialize
left = 0,right = 0,maxLength = 0. - First Pass (Left to Right):
- Iterate
ifrom0ton-1. - If
s[i] == '(', incrementleft. Else, incrementright. - If
left == right, a valid balanced substring is found. UpdatemaxLength = max(maxLength, 2 * right). - If
right > left, the sequence is broken. Resetleft = 0andright = 0.
- Iterate
- Second Pass (Right to Left):
- Reset
left = 0,right = 0. - Iterate
ifromn-1down to0. - If
s[i] == '(', incrementleft. Else, incrementright. - If
left == right, updatemaxLength = max(maxLength, 2 * left). - If
left > right, the sequence is broken (from the right). Resetleft = 0andright = 0.
- Reset
- Return
maxLength.
Walkthrough
This method cleverly avoids extra storage by using two counters, left and right, for open and closing parentheses, respectively.
Left-to-Right Scan: We iterate from the beginning of the string. We increment left for ( and right for ). Whenever left == right, we have found a valid substring of length 2 * right. If right > left at any point, it means we have an excess of closing parentheses, which makes the current substring invalid, so we reset both counters to 0.
This single scan, however, fails for cases like (() where there are excess open parentheses. At the end of the scan, left will be greater than right, and the valid () part is missed.
Right-to-Left Scan: To handle the cases with excess open parentheses, we perform a similar scan from the end of the string backwards. This time, if left > right, we reset the counters. This correctly identifies valid substrings that were missed in the first pass, like the () in (().
The final answer is the maximum length found across both scans.
class Solution { public int longestValidParentheses(String s) { int left = 0, right = 0, maxlength = 0; // Left to Right Scan for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { left++; } else { right++; } if (left == right) { maxlength = Math.max(maxlength, 2 * right); } else if (right > left) { left = right = 0; } } left = right = 0; // Right to Left Scan for (int i = s.length() - 1; i >= 0; i--) { if (s.charAt(i) == '(') { left++; } else { right++; } if (left == right) { maxlength = Math.max(maxlength, 2 * left); } else if (left > right) { left = right = 0; } } return maxlength; }}Complexity
Time
O(n)
Space
O(1)
Trade-offs
Pros
Most efficient in terms of space, using O(1) extra space.
Maintains a fast linear time complexity.
Cons
Requires two passes over the string.
The logic of why two passes are necessary might be less obvious at first glance.
Solutions
Solution
public class Solution { public int LongestValidParentheses(string s) { int n = s.Length; int[] f = new int[n + 1]; int ans = 0; for (int i = 2; i <= n; ++i) { if (s[i - 1] == ')') { if (s[i - 2] == '(') { f[i] = f[i - 2] + 2; } else { int j = i - f[i - 1] - 1; if (j > 0 && s[j - 1] == '(') { f[i] = f[i - 1] + 2 + f[j - 1]; } } ans = Math.Max(ans, f[i]); } } return ans; }}class Solution {public int longestValidParentheses(String s) { int n = s.length(); int[] f = new int[n + 1]; int ans = 0; for (int i = 2; i <= n; ++i) { if (s.charAt(i - 1) == ')') { if (s.charAt(i - 2) == '(') { f[i] = f[i - 2] + 2; } else { int j = i - f[i - 1] - 1; if (j > 0 && s.charAt(j - 1) == '(') { f[i] = f[i - 1] + 2 + f[j - 1]; } } ans = Math.max(ans, f[i]); } } return ans; }}/** * @param {string} s * @return {number} */ var longestValidParentheses = function (s) { const n = s.length; const f = new Array(n + 1).fill(0); for (let i = 2; i <= n; ++i) { if (s[i - 1] === " ) ") { if (s[i - 2] === " ( ") { f[i] = f[i - 2] + 2; } else { const j = i - f[i - 1] - 1; if (j && s[j - 1] === " ( ") { f[i] = f[i - 1] + 2 + f[j - 1]; } } } } return Math.max(...f); };class Solution {public: int longestValidParentheses(string s) { int n = s.size(); int f[n + 1]; memset(f, 0, sizeof(f)); for (int i = 2; i <= n; ++i) { if (s[i - 1] == ')') { if (s[i - 2] == '(') { f[i] = f[i - 2] + 2; } else { int j = i - f[i - 1] - 1; if (j && s[j - 1] == '(') { f[i] = f[i - 1] + 2 + f[j - 1]; } } } } return *max_element(f, f + n + 1); }};class Solution: def longestValidParentheses(self, s: str) -> int: left = right = 0 res = 0 for c in s: # from left to right, '(()' => will never hit left==right if c == '(' : left += 1 else : right += 1 if left == right : res = max ( res , 2 * left ) if left < right : left = right = 0 left = right = 0 # dont forget to reset for c in reversed ( s ): # from right to left, '())' => will never hit left==right if c == '(' : left += 1 else : right += 1 if left == right : res = max ( res , 2 * left ) if left > right : # reverse '<' to '>' left = right = 0 return res ###### class Solution : def longestValidParentheses ( self , s : str ) -> int : n = len ( s ) if n < 2 : return 0 dp = [ 0 ] * n for i in range ( 1 , n ): if s [ i ] == ')' : if s [ i - 1 ] == '(' : dp [ i ] = 2 + ( dp [ i - 2 ] if i > 1 else 0 ) else : j = i - dp [ i - 1 ] - 1 if j >= 0 and s [ j ] == '(' : dp [ i ] = 2 + dp [ i - 1 ] + dp [ j - 1 ] return max ( dp ) ###### ''' >>> s="abcdefg" >>> [print(i,",",c) for i, c in enumerate(s, 1)] 1 , a 2 , b 3 , c 4 , d 5 , e 6 , f 7 , g [None, None, None, None, None, None, None] >>> ''' class Solution : def longestValidParentheses ( self , s : str ) -> int : n = len ( s ) f = [ 0 ] * ( n + 1 ) for i , c in enumerate ( s , 1 ): # starting i from 1, not 0 if c == ")" : if i > 1 and s [ i - 2 ] == "(" : f [ i ] = f [ i - 2 ] + 2 else : j = i - f [ i - 1 ] - 1 if j and s [ j - 1 ] == "(" : f [ i ] = f [ i - 1 ] + 2 + f [ j - 1 ] return max ( f )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.