32. Longest Valid Parentheses
Problem Description
In this problem, we are given a string that consists only of the characters '(' and ')'. The goal is to find the length of the longest substring that represents a valid sequence of parentheses. A valid parentheses string is one where every '(' has a matching ')' and the pairs are well-nested. For example, in the string "(()", only the substring "()" is valid and has a length of 2.
Intuition
The key to solving this problem lies in the fact that a valid parentheses string must end with a ')' character. To systematically approach this, we use dynamic programming (DP). The solution builds up information about the substrings ending at each position in the input string, using an array (or list) to record the results.
The intuition behind the dynamic programming solution is this: We define a DP array where the ith entry, f[i]
, represents the length of the longest valid parentheses substring ending at the index i - 1
in the input string. To fill in this array, we consider two main cases:
-
If the character at position
i - 1
is '(', then no valid substring can end with this character, sof[i]
would be 0. -
If the character at position
i - 1
is ')', this is where it gets interesting. We look at two possible scenarios:a. If the character right before it (at
i - 2
) is '(', we have a pair "()", so the length of the longest valid substring ending ati - 1
would bef[i - 2] + 2
, since we're adding these two characters to whatever we had before.b. If the character at
i - 2
is also ')', the situation is a bit more complex. This might be a continuation of a previously started valid string. We first check that there's a valid string ending just before the current one (ati - f[i - 1] - 2
). If there is, and the character at the beginning of this string (ati - f[i - 1] - 2
) is '(', then the current ')' can close this, forming a new valid string, so we append the lengths:f[i - 1] + 2 + f[i - f[i - 1] - 2]
.
With this DP array filled out, all we need to do is to find the maximum value in it, which represents the length of the longest valid parentheses substring in the entire input string. The code iterates through the string only once and fills the DP array as it goes, making it an efficient solution.
Learn more about Stack and Dynamic Programming patterns.
Solution Approach
The solution presented here is a prime example of dynamic programming. Specifically, it showcases a bottom-up iterative approach to solve the problem of finding the longest valid parentheses substring. Here's how the approach is implemented:
Firstly, we initialize a list f
with a length of n + 1
, where n
is the length of the input string s
, and fill it with zeros. This list will hold our dynamic programming table, where f[i]
represents the length of the longest valid parentheses substring that ends at index i - 1
.
As we iterate over the string (1-indexed for easy reference), we only need to consider elements in s
that are closing parentheses ')'
. This is because valid substrings can only end with a closing parenthesis.
For each closing parenthesis at index i
, we have two main cases:
-
Case 1: The previous character is an opening parenthesis
'('
. This means we've found a simple pair of parentheses "()
". The length of the longest valid substring ending ati
isf[i - 2] + 2
. This consists of the length of whatever valid substring ends right before this pair plus the length of this pair (2). -
Case 2: The previous character is also a closing parenthesis
')'
. Here, we have to look back and check if there's a matching opening parenthesis that can pair with our current one. To find it, we subtract the length of the valid substring ending just before (atf[i - 1]
) fromi
and go back one more step (hencei - f[i - 1] - 2
). If we find that there's an opening parenthesis at that position, we can extend the length of the valid substring ending there by adding the length of the valid substring ending at our current position plus 2 for the current pair "()", and also add the value atf[j - 1]
, wherej
is the index we just found to check availability of a valid opening parenthesis. This step accounts for concatenating a new valid pair to an existing valid substring.
By repeatedly applying the above logic as we scan through the string, we build up our DP table. Once we have processed the entire string, we can simply return max(f)
, which will give us the length of the longest valid parentheses substring found.
The approach effectively breaks down the problem into manageable subproblems. It ensures that every position in the string is only calculated once, giving us an overall time complexity of O(n)
where n
is the length of the string. The space complexity is also O(n)
due to the additional list used for storing the lengths of longest valid substrings.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's apply the solution approach to a small example and illustrate how it works.
Suppose we are given the string s = "(()())"
.
We first initialize a list f
with a length of n + 1 = 7
(since the length of s
is 6), and fill it with zeros: f = [0, 0, 0, 0, 0, 0, 0]
.
Now, we will iterate over the string, starting from index 1:
-
i = 1
:s[i - 1] = '('
, so we do nothing because a valid substring cannot end with '('.f = [0, 0, 0, 0, 0, 0, 0]
-
i = 2
:s[i - 1] = '('
, we do nothing again for the same reason.f = [0, 0, 0, 0, 0, 0, 0]
-
i = 3
:s[i - 1] = ')'
, previous char is'('
, so we have "()", andf[i] = f[1] + 2 = 2
.f = [0, 0, 2, 0, 0, 0, 0]
-
i = 4
:s[i - 1] = '('
, do nothing.f = [0, 0, 2, 0, 0, 0, 0]
-
i = 5
:s[i - 1] = ')'
, the previous character is'('
, so "()" again,f[i] = f[3] + 2 = 4
(because we are adding the pair()
to what we had atf[3]
).f = [0, 0, 2, 0, 4, 0, 0]
-
i = 6
:s[i - 1] = ')'
, the previous character is')'
, we checki - f[i - 1] - 2 = 6 - 4 - 2 = 0
, at position0
we have'('
, so we have a longer valid substring that spans the whole current string,f[i] = f[4] + 2 + f[6 - 4 - 2] = 4 + 2 + 0
.f = [0, 0, 2, 0, 4, 0, 6]
Now our DP list f
tells us that the longest valid substring ending at any position in s
. The max value in f
is 6
, which is the length of the longest valid parentheses substring in s
.
This walkthrough demonstrates the bottom-up DP technique used to find the longest valid parentheses substring in linear time complexity, which is O(n)
.
Solution Implementation
1class Solution:
2 def longestValidParentheses(self, s: str) -> int:
3 # Get the length of the input string
4 string_length = len(s)
5 # Initialize a DP array with zeros, one extra for the base case
6 dp = [0] * (string_length + 1)
7
8 # Loop over the characters of string starting from index 1 for convenience
9 for index, char in enumerate(s, 1):
10 # We look for closing parentheses, as they mark possible ends of valid substrings
11 if char == ")":
12 # If the previous char is '(', it's a pair "()"
13 if index > 1 and s[index - 2] == "(":
14 # Add 2 to the result two positions ago in dp array
15 dp[index] = dp[index - 2] + 2
16 else:
17 # Get the index of the potential matching '('
18 match_index = index - dp[index - 1] - 1
19 # Make sure match_index is within bounds and check for '('
20 if match_index > 0 and s[match_index - 1] == "(":
21 # Add the length of the valid substring ending right before the current one,
22 # plus two for the '()' just found, plus length of valid substring before the pair
23 dp[index] = dp[index - 1] + 2 + dp[match_index - 1]
24
25 # Return the maximum length of valid parentheses found
26 return max(dp)
27
1class Solution {
2 public int longestValidParentheses(String s) {
3 int strLength = s.length();
4 int[] validLengths = new int[strLength + 1]; // Array to store the length of valid parentheses substrings at each index.
5 int maxValidLength = 0; // The maximum length of valid parentheses found.
6
7 // Iterate starting from the second character as we need to check pairs.
8 for (int i = 2; i <= strLength; ++i) {
9 // Check if the current character is a closing parenthesis.
10 if (s.charAt(i - 1) == ')') {
11 // If the previous character is an opening parenthesis, it forms a valid pair.
12 if (s.charAt(i - 2) == '(') {
13 validLengths[i] = validLengths[i - 2] + 2; // Add 2 for the valid '()' pair.
14 } else {
15 // If the previous character is also a closing parenthesis,
16 // find the position before the valid substring that starts
17 // just before the current one.
18 int prevIndex = i - validLengths[i - 1] - 1;
19 // Check if there's a matching opening parenthesis.
20 if (prevIndex > 0 && s.charAt(prevIndex - 1) == '(') {
21 validLengths[i] = validLengths[i - 1] + 2 + validLengths[prevIndex - 1]; // Add lengths of valid substrings plus the new pair.
22 }
23 }
24 // Update the maximum length if needed.
25 maxValidLength = Math.max(maxValidLength, validLengths[i]);
26 }
27 }
28
29 return maxValidLength; // Return the maximum length of valid parentheses.
30 }
31}
32
1#include <vector>
2#include <algorithm>
3#include <cstring>
4
5class Solution {
6public:
7 int longestValidParentheses(string s) {
8 int length = s.size();
9
10 // Create a dynamic programming vector with 0 initialization
11 vector<int> dp(length + 1, 0);
12
13 // Iterate over the string, starting at the second character
14 for (int i = 2; i <= length; ++i) {
15 // If the current character is a closing parenthesis
16 if (s[i - 1] == ')') {
17 // If the previous character is an opening parenthesis
18 if (s[i - 2] == '(') {
19 // Add 2 to the dynamic programming array at position i
20 // This forms a pair with the previous '('
21 dp[i] = dp[i - 2] + 2;
22 } else {
23 // Calculate the index before the previous valid substring
24 int previousIndex = i - dp[i - 1] - 1;
25 // If there's an opening parenthesis before the previous valid substring
26 if (previousIndex > 0 && s[previousIndex - 1] == '(') {
27 // Add the lengths of the currently found valid substring, the one before it,
28 // and the one before the opening parenthesis
29 dp[i] = dp[i - 1] + 2 + dp[previousIndex - 1];
30 }
31 }
32 }
33 }
34
35 // Return the maximum length found in the dp array
36 return *max_element(dp.begin(), dp.end());
37 }
38};
39
1// This function calculates the length of the longest valid (well-formed) parentheses substring.
2function longestValidParentheses(s: string): number {
3 const lengthOfString: number = s.length;
4 // Initialize an array to record the length of the longest valid parentheses ending at each position.
5 const dp: number[] = new Array(lengthOfString + 1).fill(0);
6 let maxValidLength: number = 0; // Keep track of the maximum valid length.
7
8 // Iterate through the string starting from the second character position.
9 for (let i = 2; i <= lengthOfString; i++) {
10 // Check if the current character is a closing parenthesis.
11 if (s[i - 1] === ')') {
12 // If previous character is an opening parenthesis, it's a valid pair.
13 if (s[i - 2] === '(') {
14 dp[i] = dp[i - 2] + 2;
15 } else {
16 // Check if the substring before the last valid parentheses is an opening parenthesis.
17 const previousIndex: number = i - dp[i - 1] - 1;
18 if (previousIndex > 0 && s[previousIndex - 1] === '(') {
19 dp[i] = dp[i - 1] + 2 + dp[previousIndex - 1];
20 }
21 }
22 }
23 // Update the maximum valid length found so far.
24 maxValidLength = Math.max(maxValidLength, dp[i]);
25 }
26
27 // Return the maximum valid length of the parentheses found.
28 return maxValidLength;
29}
30
Time and Space Complexity
The time complexity of the given code is O(n)
where n
is the length of the string s
. This is because the algorithm iterates through all characters of the input string once, and for each character, it performs a constant amount of work.
The space complexity of the code is O(n)
as well, due to the auxiliary space used by the list f
, which has a length of n + 1
. Each element of the list f
stores the length of the longest valid substring ending at that position, thereby requiring linear space relative to the input string length.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!