title: Longest Valid Parentheses slug: longest-valid-parentheses difficulty: hard leetcode_id: 32 leetcode_url: https://leetcode.com/problems/longest-valid-parentheses/ categories: - strings - stack - dynamic-programming patterns: - slug: dynamic-programming is_optimal: false - slug: monotonic-stack is_optimal: true function_signature: "def longest_valid_parentheses(s: str) -> int:" test_cases: visible: - input: { s: "(()" } expected: 2 - input: { s: ")()())" } expected: 4 - input: { s: "" } expected: 0 hidden: - input: { s: "()" } expected: 2 - input: { s: "()()" } expected: 4 - input: { s: "((()))" } expected: 6 - input: { s: "(()()" } expected: 4 - input: { s: ")()()((" } expected: 4 - input: { s: "((((((" } expected: 0 - input: { s: "))))))" } expected: 0 description: | Given a string containing just the characters `'('` and `')'`, return *the length of the longest valid (well-formed) parentheses substring*. A valid parentheses substring is one where every opening parenthesis `'('` has a corresponding closing parenthesis `')'` and they are properly nested. constraints: | - `0 <= s.length <= 3 * 10^4` - `s[i]` is `'('` or `')'` examples: - input: 's = "(()"' output: "2" explanation: "The longest valid parentheses substring is \"()\"." - input: 's = ")()())"' output: "4" explanation: "The longest valid parentheses substring is \"()()\"." - input: 's = ""' output: "0" explanation: "An empty string has no valid parentheses." explanation: intuition: | Imagine you're reading through a string of parentheses and trying to find the longest stretch where they're perfectly balanced. The key insight is that a valid parentheses string can be **broken by unmatched characters**. An unmatched `)` at position `i` means any valid substring must start *after* `i`. Similarly, an unmatched `(` at position `j` means any valid substring ending before `j` cannot extend past it. Think of it like this: unmatched parentheses act as **barriers** that divide the string into segments. Within each segment, we need to find how far the valid matching extends. There are two elegant ways to approach this: 1. **Stack approach**: Use a stack to track indices of unmatched `(` characters. When we see a `)`, we either match it with a `(` (pop from stack) or mark it as a barrier. The stack always holds indices that "break" the valid sequence. 2. **Dynamic Programming**: For each position, calculate the length of the longest valid substring *ending* at that position. A `)` at position `i` can extend a valid substring if there's a matching `(` available. The stack approach is more intuitive once you see it: we push indices as barriers, and the distance from the current index to the top of the stack gives us the length of the current valid segment. approach: | We'll use the **Stack Approach** as our optimal solution: **Step 1: Initialise the stack with a base index** - Push `-1` onto the stack as a "floor" or base index - This handles the edge case where a valid substring starts from index `0` - The stack will store indices of unmatched `(` characters and barrier positions   **Step 2: Iterate through each character** - For each character at index `i`: - If it's `'('`: push `i` onto the stack (potential start of valid sequence) - If it's `')'`: pop from the stack (try to match with a `(`)   **Step 3: Calculate valid length after each `)`** - After popping for a `)`: - If the stack is **empty**: this `)` is unmatched, push `i` as a new barrier - If the stack is **not empty**: calculate `i - stack.top()` as the length of the current valid substring - Update `max_length` with the maximum value seen   **Step 4: Return the result** - Return `max_length` after processing all characters   **Why this works**: The stack always contains indices that "break" valid sequences. The distance from the current index to the stack top represents how far back the current valid sequence extends. common_pitfalls: - title: Forgetting the Base Index description: | Without pushing `-1` initially, the first valid substring starting from index `0` won't be calculated correctly. For example, with `s = "()"`: - At index `0`, push `0` - At index `1`, pop `0`, stack is now empty - Without a base, we can't calculate `1 - (-1) = 2` Always initialise with `-1` to handle edge cases cleanly. wrong_approach: "Start with an empty stack" correct_approach: "Push -1 as the base index before processing" - title: Confusing Valid Substring vs Total Matches description: | This problem asks for the longest **contiguous** valid substring, not the total number of matched pairs. For `s = "()(())"`: - Total matched pairs: 3 (length 6) - But the whole string is one valid substring of length 6 For `s = "())()"`: - Total matched pairs: 2 - But longest valid substring is only 2 (`()` at the end or beginning) The unmatched `)` at index 2 breaks the string into separate segments. wrong_approach: "Count total matched pairs" correct_approach: "Track longest contiguous valid segment" - title: Using O(n) Space When O(1) is Possible description: | While the stack solution is intuitive and efficient at O(n) space, there's actually an O(1) space solution using two-pass counting. For interviews, the stack approach is typically expected, but knowing the O(1) solution demonstrates deeper understanding. wrong_approach: "Only knowing the stack approach" correct_approach: "Understand both stack O(n) and two-pass O(1) approaches" - title: Off-by-One Errors in Length Calculation description: | When calculating `i - stack.top()`, remember that this gives the length, not the ending index. For example, if `i = 5` and `stack.top() = 2`: - Length = `5 - 2 = 3` (positions 3, 4, 5) - This represents indices 3 through 5 inclusive Make sure your mental model matches: we're measuring distance, not counting indices. key_takeaways: - "**Stack for matching problems**: Using a stack to track indices (not just characters) is a powerful technique for parentheses and bracket matching" - "**Barrier concept**: Unmatched characters act as barriers that reset the valid substring count" - "**Base index trick**: Pushing `-1` as a base handles edge cases elegantly without special-casing" - "**Related problems**: Valid Parentheses (#20), Generate Parentheses (#22), and Minimum Add to Make Parentheses Valid (#921) use similar concepts" time_complexity: "O(n). We traverse the string exactly once, and each index is pushed and popped from the stack at most once." space_complexity: "O(n). In the worst case (all opening parentheses), the stack holds all n indices. The two-pass approach achieves O(1) space." solutions: - approach_name: Stack with Index Tracking is_optimal: true code: | def longest_valid_parentheses(s: str) -> int: # Stack stores indices of unmatched '(' and barrier positions # Start with -1 as base to handle valid substring starting at index 0 stack = [-1] max_length = 0 for i, char in enumerate(s): if char == '(': # Push index of '(' as potential start of valid sequence stack.append(i) else: # Pop to match this ')' with a '(' stack.pop() if not stack: # Stack empty means this ')' is unmatched # Push current index as new barrier stack.append(i) else: # Calculate length of current valid substring # Distance from current position to the last barrier current_length = i - stack[-1] max_length = max(max_length, current_length) return max_length explanation: | **Time Complexity:** O(n) — Single pass through the string. **Space Complexity:** O(n) — Stack can hold up to n indices in the worst case. The stack maintains a "barrier" at its top, representing the rightmost position that breaks valid parentheses. When we find a valid match, the distance from the current index to this barrier gives us the valid substring length. - approach_name: Dynamic Programming is_optimal: false code: | def longest_valid_parentheses(s: str) -> int: if not s: return 0 n = len(s) # dp[i] = length of longest valid substring ending at index i dp = [0] * n max_length = 0 for i in range(1, n): if s[i] == ')': if s[i - 1] == '(': # Case 1: "()" pattern - extends previous valid substring dp[i] = (dp[i - 2] if i >= 2 else 0) + 2 elif i - dp[i - 1] > 0 and s[i - dp[i - 1] - 1] == '(': # Case 2: "))" pattern - check if there's matching '(' # before the valid substring ending at i-1 dp[i] = dp[i - 1] + 2 # Add any valid substring before the matching '(' if i - dp[i - 1] >= 2: dp[i] += dp[i - dp[i - 1] - 2] max_length = max(max_length, dp[i]) return max_length explanation: | **Time Complexity:** O(n) — Single pass through the string. **Space Complexity:** O(n) — DP array of size n. For each `)` at position `i`, we determine if it can extend a valid substring: - If preceded by `(`, we have a `()` pair adding 2 to whatever came before - If preceded by `)`, we look past the valid substring ending at `i-1` to find a matching `(` - approach_name: Two-Pass Counting is_optimal: false code: | def longest_valid_parentheses(s: str) -> int: # O(1) space solution using two passes max_length = 0 left = right = 0 # Left to right pass for char in s: if char == '(': left += 1 else: right += 1 if left == right: # Balanced - this is a valid substring max_length = max(max_length, 2 * right) elif right > left: # Too many ')' - reset counters left = right = 0 # Right to left pass (handles excess '(' cases) left = right = 0 for char in reversed(s): if char == '(': left += 1 else: right += 1 if left == right: max_length = max(max_length, 2 * left) elif left > right: # Too many '(' - reset counters left = right = 0 return max_length explanation: | **Time Complexity:** O(n) — Two passes through the string. **Space Complexity:** O(1) — Only uses counter variables. This clever approach counts left and right parentheses. When counts match, we have a valid substring. We need two passes because a single pass can't handle both excess `(` and excess `)` cases. Left-to-right handles excess `)`, right-to-left handles excess `(`.