Skip to content

Commit 7044acc

Browse files
authored
Merge pull request #191 from BrianLusina/feat/algorithms-stack-longest-valid-parentheses
feat(algorithms, stack): longest valid parentheses
2 parents d11f015 + 58ec751 commit 7044acc

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@
386386
* [Test Decimal To Binary](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/decimal_to_binary/test_decimal_to_binary.py)
387387
* Decode String
388388
* [Test Decode String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/decode_string/test_decode_string.py)
389+
* Longest Valid Parentheses
390+
* [Test Longest Valid Parentheses](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/longest_valid_parentheses/test_longest_valid_parentheses.py)
389391
* Minimum String Length After Removing Substrings
390392
* [Test Min Str Length After Removing Substrings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/minimum_string_length_after_removing_substrings/test_min_str_length_after_removing_substrings.py)
391393
* Nextgreater
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Longest Valid Parentheses
2+
3+
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.
4+
5+
A substring is considered valid if:
6+
7+
1. Every opening parenthesis ‘(’ has a corresponding closing parenthesis ‘)’.
8+
2. The pairs of parentheses are correctly nested.
9+
10+
Return the length of this longest valid substring.
11+
12+
## Constraints
13+
14+
- 0 <= s.length <= 3 * 10^4
15+
- s[i] is '(', or ')'.
16+
17+
## Examples
18+
19+
Example 1:
20+
```text
21+
Input: s = "(()"
22+
Output: 2
23+
Explanation: The longest valid parentheses substring is "()".
24+
```
25+
26+
Example 2:
27+
```text
28+
Input: s = ")()())"
29+
Output: 4
30+
Explanation: The longest valid parentheses substring is "()()".
31+
```
32+
33+
Example 3:
34+
```text
35+
Input: s = ""
36+
Output: 0
37+
```
38+
39+
## Topics
40+
41+
- String
42+
- Dynamic Programming
43+
- Stack
44+
45+
## Solution
46+
47+
The problem asks for the length of the longest valid (well-formed) parenthesis substring. This type of matching problem,
48+
where you need to pair opening brackets with their corresponding closing brackets, is a classic example of a problem that
49+
lends itself to a stack pattern. The stack’s last in, first out (LIFO) property naturally models the nesting structure of
50+
parentheses: the last opening parenthesis must be the first one to be closed.
51+
52+
The essence of this algorithm is to use the stack to keep track of the indexes of parentheses that have not yet been
53+
matched. Instead of just pushing the characters themselves, pushing their indexes allows us to calculate lengths. The
54+
stack maintains a “base” index at the bottom, which marks the start of a potential valid substring. When a closing
55+
parenthesis, ‘)’, finds a matching opening parenthesis, ‘(’, (by popping its index from the stack), the length of the
56+
newly formed valid substring is calculated as the difference between the current index and the new top of the stack.
57+
58+
The following steps can be performed to implement the algorithm above:
59+
60+
1. Initialize a stack, indexStack, and push an initial index of -1. This value acts as a sentinel or a “base” for the
61+
first potential valid substring.
62+
2. Next, iterate over the input string from the current index to the right (length of the input string), character by
63+
character.
64+
- **Opening parenthesis (**: If we encounter an opening parenthesis, we push its index onto the stack.
65+
- **Closing parenthesis )**: If we encounter a closing parenthesis:
66+
- We pop the top element from the indexStack.
67+
- If the stack becomes empty after popping, it means the current ‘)’ does not have a matching ‘(’. In this case, we
68+
push the current ‘)’s index onto the stack to serve as the new “base” for any future valid substrings.
69+
- Otherwise, if the stack is not empty, this means a valid pair has been formed. The length of this valid substring
70+
is the difference between the currentIndex and the index at the new top of the stack (which is the index right
71+
before the start of this valid pair). Compare this length with the maximum length, currentLength, found so far,
72+
and update it if necessary.
73+
3. After the iteration ends, the maximum length, as recorded using max(maxLength, currentLength), is the answer.
74+
75+
### Time Complexity
76+
77+
The solution involves a single pass through the input string s. The loop runs exactly n times, where n is the length of
78+
the string, and inside the loop, all operations (push, pop, top on the stack) take constant time, i.e., O(1). Therefore,
79+
the total time complexity is dominated by the loop, resulting in O(n).
80+
81+
### Space Complexity
82+
83+
In the worst-case scenario, the input string, s, may consist entirely of opening parenthesis (e.g., “((((...”). In this
84+
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
85+
is the length of the string. Therefore, the space complexity is O(n).
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
def longest_valid_parentheses(s: str) -> int:
2+
# Stack to store indexes. We initialize it with -1 to serve as a
3+
# sentinel value. This handles the edge case of a valid substring
4+
# starting from index 0, allowing us to calculate its length correctly.
5+
stack = [-1]
6+
# Variable to store the maximum length of valid parentheses found so far.
7+
max_length = 0
8+
9+
for index, char in enumerate(s):
10+
# If the character is an opening parenthesis, push its index onto the stack. It represents a potential start of
11+
# a valid sequence
12+
if char == "(":
13+
stack.append(index)
14+
elif char == ")":
15+
stack.pop()
16+
# iF the stack is now empty, it means the current ')' has no matching '('. We push the current index to act
17+
# as a new base or starting point for the next potential valid substring
18+
if len(stack) == 0:
19+
stack.append(index)
20+
else:
21+
# If the stack is not empty, a valid pair was just formed
22+
# The new top of the stack holds the index of the character just 'before' the start of the current valid
23+
# substring. The length is the difference between the current index and that 'base' index
24+
current_length = index - stack[len(stack) - 1]
25+
26+
# Update the overall maximum length if the current one is greater
27+
max_length = max(max_length, current_length)
28+
29+
return max_length
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import unittest
2+
from parameterized import parameterized
3+
from algorithms.stack.longest_valid_parentheses import longest_valid_parentheses
4+
5+
LONGEST_VALID_PARENTHESES_TEST_CASES = [
6+
("(()", 2),
7+
(")()())", 4),
8+
("", 0),
9+
("())()", 2),
10+
("(())", 4),
11+
(")(", 0),
12+
("(", 0),
13+
("()", 2),
14+
("))(((", 0),
15+
("(()())", 6),
16+
(")()((()))((((", 8),
17+
("()(()))", 6),
18+
("(()))())(", 4),
19+
]
20+
21+
22+
class LongestValidParenthesesTestCase(unittest.TestCase):
23+
@parameterized.expand(LONGEST_VALID_PARENTHESES_TEST_CASES)
24+
def test_longest_valid_parentheses(self, s: str, expected: int):
25+
actual = longest_valid_parentheses(s)
26+
self.assertEqual(expected, actual)
27+
28+
29+
if __name__ == "__main__":
30+
unittest.main()

0 commit comments

Comments
 (0)