Problem
Given a string containing only ( and ) characters, return the minimum number of parentheses you must insert โ anywhere in the string โ to make it valid. A string is valid when every opening bracket has a matching closing bracket, and every closing bracket has a matching opening bracket to its left.
Example:
- Input:
s = "())((" - Output:
3 - Explanation: There is one unmatched
)that needs a(added before it, and two unmatched(that each need a)added after them.
Counter-example: "(())" needs 0 insertions because every bracket has a match; ")(" needs 2 insertions even though the character count is balanced, because the ) comes before the (.
Intuition
Walk through the string once and count how many brackets are "stranded." A ) is stranded when there is no earlier unmatched ( to pair with; a ( is stranded when it reaches the end of the string without a ). The answer is the total number of stranded brackets, because each one requires exactly one insertion to match it.
Approach 1 โ Stack
Use a stack to explicitly track unmatched ( characters as you scan left to right. When you see a ), pop from the stack if possible (a match was found); otherwise increment a counter for unmatched ). At the end, the stack holds all unmatched (.
- Initialize an empty stack and a counter
open_needed = 0. - For each character in the string:
- If
(, push it onto the stack. - If
), pop from the stack if it is non-empty (a match). Otherwise incrementopen_needed.
- If
- Return
open_needed + len(stack).
1def minAddToMakeValid(s: str) -> int:
2 stack = []
3 open_needed = 0 # count of ')' with no matching '(' to their left
4
5 for char in s:
6 if char == '(':
7 stack.append(char)
8 else:
9 if stack:
10 stack.pop() # this ')' matches the most recent unmatched '('
11 else:
12 open_needed += 1 # no '(' available; must insert one
13
14 return open_needed + len(stack) # len(stack) = count of unmatched '('Time: O(n) โ one pass through the string.
Space: O(n) โ the stack can hold up to n characters in the worst case (e.g. "((((").
Approach 2 โ Two Counters (Optimal Space)
Since we only need counts and not positions, replace the stack with two integer counters: open_needed for unmatched ) and close_needed for unmatched (. This eliminates the stack entirely.
- Initialize
open_needed = 0andclose_needed = 0. - For each character:
- If
(, incrementclose_needed(this(is waiting for a future)). - If
), decrementclose_neededif it is positive (a match was found). Otherwise incrementopen_needed(need to insert a().
- If
- Return
open_needed + close_needed.
1def minAddToMakeValid(s: str) -> int:
2 open_needed = 0 # unmatched ')' seen so far โ need a '(' inserted before each
3 close_needed = 0 # unmatched '(' seen so far โ need a ')' inserted after each
4
5 for char in s:
6 if char == '(':
7 close_needed += 1 # this '(' is waiting for a future ')'
8 else:
9 if close_needed > 0:
10 close_needed -= 1 # matched with the most recent unmatched '('
11 else:
12 open_needed += 1 # no '(' to match; must insert one before this ')'
13
14 return open_needed + close_neededTime: O(n) โ one pass through the string.
Space: O(1) โ only two integer counters regardless of input size.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Stack | O(n) | O(n) | When you need to know the actual positions of unmatched brackets, not just the count |
| Two Counters | O(n) | O(1) | Default choice โ only the minimum count is needed |
Common Mistakes
- Checking
abs(opens - closes)as the answer:")("has one(and one), soabs(1 - 1) = 0, but the correct answer is 2. Order matters โ the)comes before the(with no way to match it. - Forgetting to handle both directions of imbalance: Some solutions only count unmatched
)and miss unmatched(at the end. You need bothopen_needed(for stranded)) andclose_needed(for stranded(). - Decrementing
close_neededbelow zero: When seeing a)with no unmatched(to pair with, decrementing instead of clamping would make future matches incorrect. Guard the decrement withif close_needed > 0. - Using
net = opens - closesthen returningabs(net): This fails on inputs like")))((("where the imbalance is in both directions.netwould be 0 but the correct answer is 6. - Confusing which counter tracks which bracket type:
open_neededcounts how many(you need to add, triggered by seeing an unmatched).close_neededcounts how many)you need to add, triggered by seeing an unmatched(. The names refer to what is needed, not what was seen.
Related Problems
valid-parenthesesโ checks if a string is already valid; the same left-to-right matching logiclongest-valid-parenthesesโ finds the longest valid substring rather than the minimum insertionsgenerate-parenthesesโ generates all valid strings of n pairs; uses the same open/close balance invariantvalid-parenthesis-stringโ extends this problem with*wildcards that can become either bracket or be emptyminimum-deletions-to-make-string-balancedโ a related problem where you remove characters instead of adding them