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

    &nbsp;

    **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 `(`)

    &nbsp;

    **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

    &nbsp;

    **Step 4: Return the result**

    - Return `max_length` after processing all characters

    &nbsp;

    **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 `(`.
