Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@
* [Test Decimal To Binary](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/decimal_to_binary/test_decimal_to_binary.py)
* Decode String
* [Test Decode String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/decode_string/test_decode_string.py)
* Longest Valid Parentheses
* [Test Longest Valid Parentheses](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/stack/longest_valid_parentheses/test_longest_valid_parentheses.py)
* Minimum String Length After Removing Substrings
* [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)
* Nextgreater
Expand Down
85 changes: 85 additions & 0 deletions algorithms/stack/longest_valid_parentheses/README.md
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).
29 changes: 29 additions & 0 deletions algorithms/stack/longest_valid_parentheses/__init__.py
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
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()
Loading