Facebook Pixel

921. Minimum Add to Make Parentheses Valid

Problem Description

You are given a string s that contains only parentheses characters '(' and ')'. A valid parentheses string must follow these rules:

  • It can be an empty string
  • It can be formed by concatenating two valid strings (like AB where both A and B are valid)
  • It can be formed by wrapping a valid string with parentheses (like (A) where A is valid)

In simpler terms, every opening parenthesis '(' must have a matching closing parenthesis ')' that comes after it, and they must be properly nested.

Your task is to find the minimum number of parentheses (either '(' or ')') that you need to insert at any position in the string to make it valid.

For example:

  • If s = "()))", you need to add 2 parentheses. You could add '(' at the beginning and another '(' after the first character to get "(()())"
  • If s = "(((", you need to add 3 closing parentheses ')' to balance the opening ones

The solution uses a stack-based approach where:

  • When encountering '(', it gets pushed to the stack
  • When encountering ')', if there's a matching '(' on top of the stack, they cancel out (pop the stack); otherwise, the ')' is unmatched and gets pushed to the stack
  • After processing all characters, the stack contains all unmatched parentheses, and its size equals the minimum additions needed
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to track unmatched parentheses. When we see a valid pair (an opening '(' followed eventually by a closing ')'), these two cancel each other out and don't require any additions.

Think of it like balancing a scale:

  • Every '(' adds weight to the left side
  • Every ')' adds weight to the right side
  • When we can match a ')' with a previous '(', they balance out

The challenge is that order matters - a ')' can only match with a '(' that comes before it. This naturally leads us to use a stack:

  1. When we see '(', we're creating a "debt" that needs to be paid with a future ')'
  2. When we see ')', we check if there's an unpaid debt (an unmatched '(' in our stack)
    • If yes, they match! We remove the debt (pop from stack)
    • If no, this ')' itself becomes unmatched and needs a '(' added somewhere before it

After processing the entire string, whatever remains in our stack represents:

  • Unmatched '(' characters that need ')' additions
  • Unmatched ')' characters that need '(' additions

The beauty of this approach is that we don't need to actually decide where to insert the parentheses - we just count how many are unmatched. Each unmatched parenthesis requires exactly one addition to balance it out, so the stack size at the end gives us our answer directly.

Learn more about Stack and Greedy patterns.

Solution Approach

The solution implements a greedy stack-based approach to count unmatched parentheses.

Algorithm Steps:

  1. Initialize an empty stack - This will store all unmatched parentheses as we process the string.

  2. Iterate through each character c in the string s:

    • If c is a closing parenthesis ')':
      • Check if the stack is not empty AND the top element is an opening parenthesis '('
      • If both conditions are true, we have found a matching pair:
        • Pop the '(' from the stack (successful match)
      • Otherwise, this ')' is unmatched:
        • Push ')' onto the stack
    • If c is an opening parenthesis '(':
      • Always push it onto the stack (it's waiting for a future match)
  3. Return the stack size - After processing all characters, the stack contains only unmatched parentheses. Each unmatched parenthesis requires exactly one addition to balance it.

Why This Works:

The greedy nature of the algorithm ensures we match parentheses as soon as possible. When we see a ')', we immediately try to match it with the most recent unmatched '('. This is optimal because:

  • Matching early prevents accumulation of unmatched parentheses
  • Each match reduces the number of additions needed by 2 (one '(' and one ')' that would otherwise need partners)

Time Complexity: O(n) where n is the length of the string - we process each character once.

Space Complexity: O(n) in the worst case when all parentheses are unmatched (e.g., all '(' or all ')').

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the example s = "())(" step by step to illustrate how the solution works.

Initial State:

  • String: "())("
  • Stack: [] (empty)
  • Position: 0

Step 1: Process '(' at index 0

  • Character is '(', so push it to stack
  • Stack: ['(']

Step 2: Process ')' at index 1

  • Character is ')'
  • Check: Is stack non-empty AND top element is '('? Yes!
  • We found a match! Pop '(' from stack
  • Stack: [] (empty again)

Step 3: Process ')' at index 2

  • Character is ')'
  • Check: Is stack non-empty AND top element is '('? No (stack is empty)
  • This ')' is unmatched, push it to stack
  • Stack: [')']

Step 4: Process '(' at index 3

  • Character is '(', so push it to stack
  • Stack: [')', '(']

Final Result:

  • Stack contains: [')', '(']
  • Stack size = 2
  • Answer: 2 (we need to add 2 parentheses to make the string valid)

To verify: The unmatched ')' at position 2 needs a '(' before it, and the unmatched '(' at position 3 needs a ')' after it. We could transform the string to "(())())" by adding '(' at the beginning and ')' at the end, making it valid with exactly 2 additions.

Solution Implementation

1class Solution:
2    def minAddToMakeValid(self, s: str) -> int:
3        """
4        Calculate minimum number of parentheses to add to make the string valid.
5        A valid string has all parentheses properly matched.
6      
7        Args:
8            s: Input string containing only '(' and ')' characters
9          
10        Returns:
11            Minimum number of parentheses needed to make string valid
12        """
13        # Use stack to track unmatched parentheses
14        stack = []
15      
16        # Process each character in the string
17        for char in s:
18            # If current char is ')' and top of stack is '(', we have a match
19            if char == ')' and stack and stack[-1] == '(':
20                # Remove the matched opening parenthesis
21                stack.pop()
22            else:
23                # Add unmatched parenthesis (either '(' or unmatched ')')
24                stack.append(char)
25      
26        # Remaining items in stack are all unmatched parentheses
27        # Each needs a corresponding parenthesis to be valid
28        return len(stack)
29
1class Solution {
2    /**
3     * Calculates the minimum number of parentheses additions needed to make a valid string.
4     * Uses a stack to track unmatched parentheses.
5     * 
6     * @param s Input string containing '(' and ')' characters
7     * @return Minimum number of parentheses to add to make the string valid
8     */
9    public int minAddToMakeValid(String s) {
10        // Stack to store unmatched parentheses
11        Deque<Character> stack = new ArrayDeque<>();
12      
13        // Process each character in the string
14        for (char currentChar : s.toCharArray()) {
15            // Check if current character can form a valid pair with top of stack
16            if (currentChar == ')' && !stack.isEmpty() && stack.peek() == '(') {
17                // Found a matching pair, remove the opening parenthesis
18                stack.pop();
19            } else {
20                // No match found, add current character to stack
21                stack.push(currentChar);
22            }
23        }
24      
25        // The size of stack represents unmatched parentheses
26        // Each unmatched parenthesis needs a corresponding one to be added
27        return stack.size();
28    }
29}
30
1class Solution {
2public:
3    int minAddToMakeValid(string s) {
4        // Use a string as a stack to keep track of unmatched parentheses
5        string stack;
6      
7        // Iterate through each character in the input string
8        for (char c : s) {
9            // If we encounter a closing parenthesis and there's a matching opening parenthesis
10            // on top of the stack, we can form a valid pair and remove the opening parenthesis
11            if (c == ')' && !stack.empty() && stack.back() == '(') {
12                stack.pop_back();
13            }
14            else {
15                // Otherwise, push the current parenthesis onto the stack
16                // This handles both unmatched opening '(' and closing ')' parentheses
17                stack.push_back(c);
18            }
19        }
20      
21        // The size of the stack represents the number of unmatched parentheses
22        // which equals the minimum number of additions needed to make the string valid
23        return stack.size();
24    }
25};
26
1/**
2 * Calculates the minimum number of parentheses additions needed to make a valid parentheses string
3 * @param s - Input string containing only '(' and ')' characters
4 * @returns The minimum number of parentheses that must be added to make the string valid
5 */
6function minAddToMakeValid(s: string): number {
7    // Stack to keep track of unmatched parentheses
8    const unmatchedParentheses: string[] = [];
9  
10    // Iterate through each character in the string
11    for (const character of s) {
12        // Check if current character is ')' and can be matched with a '(' from the stack
13        if (character === ')' && unmatchedParentheses.length > 0 && unmatchedParentheses.at(-1) === '(') {
14            // Found a matching pair, remove the '(' from stack
15            unmatchedParentheses.pop();
16        } else {
17            // Either it's an opening parenthesis or an unmatched closing parenthesis
18            // Add to stack for later processing
19            unmatchedParentheses.push(character);
20        }
21    }
22  
23    // The remaining items in stack are all unmatched parentheses
24    // Each one needs a corresponding parenthesis to be valid
25    return unmatchedParentheses.length;
26}
27

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. This is because the algorithm iterates through each character in the string exactly once, performing constant-time operations (checking conditions, pushing to stack, or popping from stack) for each character.

The space complexity is O(n), where n is the length of the string s. In the worst-case scenario, all characters in the string are unmatched (for example, a string of all '(' or all ')'), which means all characters would be pushed onto the stack, requiring O(n) additional space.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrectly Counting Matches Instead of Unmatched Parentheses

A common mistake is trying to count successful matches and then calculating unmatched parentheses indirectly. This often leads to complex logic and off-by-one errors.

Incorrect Approach:

def minAddToMakeValid(self, s: str) -> int:
    matches = 0
    open_count = 0
    close_count = 0
  
    for char in s:
        if char == '(':
            open_count += 1
        else:
            if open_count > 0:
                matches += 1
                open_count -= 1
            else:
                close_count += 1
  
    # Bug: This doesn't correctly handle all cases
    return len(s) - matches * 2  # Wrong!

Why it fails: This approach tries to count matches and derive unmatched count, but the calculation becomes error-prone and doesn't properly track remaining unmatched opening parentheses.

Correct Fix: Directly track unmatched parentheses using the stack approach shown in the solution.

Pitfall 2: Using Counter Variables Instead of Stack

Some might try to optimize by using two counters instead of a stack, but implement it incorrectly:

Incorrect Implementation:

def minAddToMakeValid(self, s: str) -> int:
    open_needed = 0
    close_needed = 0
  
    for char in s:
        if char == '(':
            open_needed += 1  # Wrong: should be close_needed
        else:
            if open_needed > 0:  # Wrong: should check close_needed
                open_needed -= 1
            else:
                close_needed += 1
  
    return open_needed + close_needed

Correct Counter-Based Solution:

def minAddToMakeValid(self, s: str) -> int:
    open_unmatched = 0  # Count of unmatched '('
    close_unmatched = 0  # Count of unmatched ')'
  
    for char in s:
        if char == '(':
            open_unmatched += 1
        else:  # char == ')'
            if open_unmatched > 0:
                open_unmatched -= 1  # Found a match
            else:
                close_unmatched += 1  # No '(' to match with
  
    return open_unmatched + close_unmatched

Pitfall 3: Misunderstanding Stack Behavior

Some developers might think they need to check the entire stack or implement complex matching logic:

Overcomplicated Approach:

def minAddToMakeValid(self, s: str) -> int:
    stack = []
  
    for char in s:
        if char == ')':
            # Unnecessary: searching through entire stack
            found = False
            temp = []
            while stack:
                top = stack.pop()
                if top == '(' and not found:
                    found = True
                else:
                    temp.append(top)
            if not found:
                temp.append(')')
            stack.extend(reversed(temp))
        else:
            stack.append(char)
  
    return len(stack)

Why it's wrong: The greedy approach only needs to check the immediate top of the stack. Looking deeper violates the proper nesting requirement and overcomplicates the solution.

Key Takeaway

The elegance of the stack solution lies in its simplicity: only check the top element when encountering a ')'. If it's '(', they match and both are removed. Otherwise, the ')' is unmatched and gets added to the stack. This greedy matching ensures optimal results while maintaining clean, understandable code.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More