-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms, stack): longest valid parentheses #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
BrianLusina
merged 2 commits into
main
from
feat/algorithms-stack-longest-valid-parentheses
Mar 23, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Longest Valid Parentheses | ||
|
|
||
| You are given a string composed entirely of ‘(’ and ‘)’ characters. Your goal is to identify the longest contiguous segment (substring) within this string that represents a “well-formed” or “valid” sequence of parentheses. | ||
|
|
||
| A substring is considered valid if: | ||
|
|
||
| 1. Every opening parenthesis ‘(’ has a corresponding closing parenthesis ‘)’. | ||
| 2. The pairs of parentheses are correctly nested. | ||
|
|
||
| Return the length of this longest valid substring. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - 0 <= s.length <= 3 * 10^4 | ||
| - s[i] is '(', or ')'. | ||
|
|
||
| ## Examples | ||
|
|
||
| Example 1: | ||
| ```text | ||
| Input: s = "(()" | ||
| Output: 2 | ||
| Explanation: The longest valid parentheses substring is "()". | ||
| ``` | ||
|
|
||
| Example 2: | ||
| ```text | ||
| Input: s = ")()())" | ||
| Output: 4 | ||
| Explanation: The longest valid parentheses substring is "()()". | ||
| ``` | ||
|
|
||
| Example 3: | ||
| ```text | ||
| Input: s = "" | ||
| Output: 0 | ||
| ``` | ||
|
|
||
| ## Topics | ||
|
|
||
| - String | ||
| - Dynamic Programming | ||
| - Stack | ||
|
|
||
| ## Solution | ||
|
|
||
| The problem asks for the length of the longest valid (well-formed) parenthesis substring. This type of matching problem, | ||
| where you need to pair opening brackets with their corresponding closing brackets, is a classic example of a problem that | ||
| lends itself to a stack pattern. The stack’s last in, first out (LIFO) property naturally models the nesting structure of | ||
| parentheses: the last opening parenthesis must be the first one to be closed. | ||
|
|
||
| The essence of this algorithm is to use the stack to keep track of the indexes of parentheses that have not yet been | ||
| matched. Instead of just pushing the characters themselves, pushing their indexes allows us to calculate lengths. The | ||
| stack maintains a “base” index at the bottom, which marks the start of a potential valid substring. When a closing | ||
| parenthesis, ‘)’, finds a matching opening parenthesis, ‘(’, (by popping its index from the stack), the length of the | ||
| newly formed valid substring is calculated as the difference between the current index and the new top of the stack. | ||
|
|
||
| The following steps can be performed to implement the algorithm above: | ||
|
|
||
| 1. Initialize a stack, indexStack, and push an initial index of -1. This value acts as a sentinel or a “base” for the | ||
| first potential valid substring. | ||
| 2. Next, iterate over the input string from the current index to the right (length of the input string), character by | ||
| character. | ||
| - **Opening parenthesis (**: If we encounter an opening parenthesis, we push its index onto the stack. | ||
| - **Closing parenthesis )**: If we encounter a closing parenthesis: | ||
| - We pop the top element from the indexStack. | ||
| - If the stack becomes empty after popping, it means the current ‘)’ does not have a matching ‘(’. In this case, we | ||
| push the current ‘)’s index onto the stack to serve as the new “base” for any future valid substrings. | ||
| - Otherwise, if the stack is not empty, this means a valid pair has been formed. The length of this valid substring | ||
| is the difference between the currentIndex and the index at the new top of the stack (which is the index right | ||
| before the start of this valid pair). Compare this length with the maximum length, currentLength, found so far, | ||
| and update it if necessary. | ||
| 3. After the iteration ends, the maximum length, as recorded using max(maxLength, currentLength), is the answer. | ||
|
|
||
| ### Time Complexity | ||
|
|
||
| The solution involves a single pass through the input string s. The loop runs exactly n times, where n is the length of | ||
| the string, and inside the loop, all operations (push, pop, top on the stack) take constant time, i.e., O(1). Therefore, | ||
| the total time complexity is dominated by the loop, resulting in O(n). | ||
|
|
||
| ### Space Complexity | ||
|
|
||
| In the worst-case scenario, the input string, s, may consist entirely of opening parenthesis (e.g., “((((...”). In this | ||
| case, the index of every character will be pushed onto the stack, where the size of the stack can grow up to n, where n | ||
| is the length of the string. Therefore, the space complexity is O(n). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| def longest_valid_parentheses(s: str) -> int: | ||
| # Stack to store indexes. We initialize it with -1 to serve as a | ||
| # sentinel value. This handles the edge case of a valid substring | ||
| # starting from index 0, allowing us to calculate its length correctly. | ||
| stack = [-1] | ||
| # Variable to store the maximum length of valid parentheses found so far. | ||
| max_length = 0 | ||
|
|
||
| for index, char in enumerate(s): | ||
| # If the character is an opening parenthesis, push its index onto the stack. It represents a potential start of | ||
| # a valid sequence | ||
| if char == "(": | ||
| stack.append(index) | ||
| elif char == ")": | ||
| stack.pop() | ||
| # iF the stack is now empty, it means the current ')' has no matching '('. We push the current index to act | ||
| # as a new base or starting point for the next potential valid substring | ||
| if len(stack) == 0: | ||
| stack.append(index) | ||
| else: | ||
| # If the stack is not empty, a valid pair was just formed | ||
| # The new top of the stack holds the index of the character just 'before' the start of the current valid | ||
| # substring. The length is the difference between the current index and that 'base' index | ||
| current_length = index - stack[len(stack) - 1] | ||
|
|
||
| # Update the overall maximum length if the current one is greater | ||
| max_length = max(max_length, current_length) | ||
|
|
||
| return max_length |
30 changes: 30 additions & 0 deletions
30
algorithms/stack/longest_valid_parentheses/test_longest_valid_parentheses.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import unittest | ||
| from parameterized import parameterized | ||
| from algorithms.stack.longest_valid_parentheses import longest_valid_parentheses | ||
|
|
||
| LONGEST_VALID_PARENTHESES_TEST_CASES = [ | ||
| ("(()", 2), | ||
| (")()())", 4), | ||
| ("", 0), | ||
| ("())()", 2), | ||
| ("(())", 4), | ||
| (")(", 0), | ||
| ("(", 0), | ||
| ("()", 2), | ||
| ("))(((", 0), | ||
| ("(()())", 6), | ||
| (")()((()))((((", 8), | ||
| ("()(()))", 6), | ||
| ("(()))())(", 4), | ||
| ] | ||
|
|
||
|
|
||
| class LongestValidParenthesesTestCase(unittest.TestCase): | ||
| @parameterized.expand(LONGEST_VALID_PARENTHESES_TEST_CASES) | ||
| def test_longest_valid_parentheses(self, s: str, expected: int): | ||
| actual = longest_valid_parentheses(s) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.