[Medium] 921. Minimum Add to Make Parentheses Valid
A parentheses string is valid if and only if:
- It is the empty string,
- It can be written as
AB(Aconcatenated withB), whereAandBare valid strings, or - It can be written as
(A), whereAis a valid string.
You are given a parentheses string s. In one move, you can insert a parenthesis at any position of the string.
- For example, if
s = "()))", you can insert an opening parenthesis to be"(()))"or a closing parenthesis to be"())))".
Return the minimum number of moves required to make s valid.
Examples
Example 1:
Input: s = "())"
Output: 1
Explanation: Insert '(' at the beginning: "()())"
Example 2:
Input: s = "((("
Output: 3
Explanation: Insert ')' at the end: "((()))"
Example 3:
Input: s = "()))(("
Output: 4
Explanation: Need to add 2 '(' at the beginning and 2 ')' at the end
Constraints
1 <= s.length <= 1000s[i]is either'('or')'.
Thinking Process
- Counter-Based: Use counters instead of stack for single bracket type
- Unmatched closing: Tracked by
right(need to add opening) - Unmatched opening: Tracked by
left(need to add closing)
- Unmatched closing: Tracked by
- Stack matches nested or LIFO structure (parentheses, monotonic scans).
- Push on open / larger; pop when the current element resolves pending work.
- Monotonic stack finds next greater/smaller in O(n).
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Monotonic stack | O(n) | O(n) | Next greater/smaller element |
| Parentheses matching (this problem) | O(n) | O(n) | Push open, pop on close |
| Expression evaluation | O(n) | O(n) | Operand + operator stacks |
| Stack simulation | O(n) | O(n) | Process in LIFO order |
Solution
Time Complexity: O(n)
Space Complexity: O(1)
Use counters to track unmatched opening and closing parentheses. When we see a closing parenthesis, match it with an existing opening if possible, otherwise we need to add an opening parenthesis. At the end, add closing parentheses for any remaining unmatched opening parentheses.
class Solution:
def minAddToMakeValid(self, s):
left = 0
right = 0
for ch in s:
if ch == '(':
left += 1
elif ch == ')':
if left > 0:
left -= 1
else:
right += 1
return left + right
Solution Explanation
Approach: Parentheses matching (this problem)
Key idea: 1. Counter-Based: Use counters instead of stack for single bracket type
How the code works:
- Counter-Based: Use counters instead of stack for single bracket type
- Unmatched closing: Tracked by
right(need to add opening) - Unmatched opening: Tracked by
left(need to add closing) - Stack matches nested or LIFO structure (parentheses, monotonic scans).
- Push on open / larger; pop when the current element resolves pending work.
- Monotonic stack finds next greater/smaller in O(n).
- Unmatched closing: Tracked by
Walkthrough — input s = "())", expected output 1:
Insert ‘(‘ at the beginning: “()())”
| Aspect | Complexity | |——–|————| | Time | O(n) - Single pass through string | | Space | O(1) - Only using two integer variables |
Algorithm Breakdown
1. Initialize Counters
def min_add_greedy(s: str) -> int:
left = right = 0
for ch in s:
if ch == "(":
left += 1
elif left > 0:
left -= 1
else:
right += 1
return left + right
left: Tracks unmatched opening parenthesesright: Tracks unmatched closing parentheses (need to add opening)
2. Process Opening Parentheses
def min_add_stack(s: str) -> int:
st: list[str] = []
min_add = 0
for c in s:
if c == "(":
st.append(c)
elif st:
st.pop()
else:
min_add += 1
return min_add + len(st)
- Increment
leftcounter - This represents an opening that needs to be closed later
3. Process Closing Parentheses
def min_add_stack_alt(s: str) -> int:
st: list[str] = []
extra_close = 0
for c in s:
if c == "(":
st.append(c)
elif st:
st.pop()
else:
extra_close += 1
return extra_close + len(st)
- If
left > 0: Match with existing opening → decrementleft - If
left == 0: No opening to match → incrementright(need to add an opening)
4. Calculate Final Answer
# Not recommended for this problem
# DP would be O(n²) time and space
right: Number of opening parentheses to add (for unmatched closing)left: Number of closing parentheses to add (for unmatched opening)- Sum: Total minimum additions needed
Complexity
| Aspect | Complexity | |——–|————| | Time | O(n) - Single pass through string | | Space | O(1) - Only using two integer variables |
Common Mistakes
- Empty string:
""→0(already valid) - All opening:
"((("→3(need 3 closing) - All closing:
")))"→3(need 3 opening) - Already valid:
"()()"→0 - Nested valid:
"((()))"→0 -
Mixed:
"()))(("→4(2 opening + 2 closing) - Only tracking one type: Forgetting to add
leftat the end - Wrong condition: Using
left >= 0instead ofleft > 0 - Not greedy: Trying to optimize placement instead of just counting
- Off-by-one errors: In length calculations
Detailed Example Walkthrough
Example 1: s = "())"
Step 0: Initialize
left = 0
right = 0
Step 1: ch = '('
Opening → left = 1
State: left=1, right=0
Step 2: ch = ')'
Closing → left > 0? Yes → left = 0
State: left=0, right=0
Step 3: ch = ')'
Closing → left > 0? No → right = 1
State: left=0, right=1
Final: left + right = 0 + 1 = 1 ✓
Example 2: s = "((("
Step 0: Initialize
left = 0
right = 0
Step 1: ch = '('
Opening → left = 1
State: left=1, right=0
Step 2: ch = '('
Opening → left = 2
State: left=2, right=0
Step 3: ch = '('
Opening → left = 3
State: left=3, right=0
Final: left + right = 3 + 0 = 3 ✓
Example 3: s = "()))(("
Step 0: Initialize
left = 0
right = 0
Step 1: ch = '('
Opening → left = 1
State: left=1, right=0
Step 2: ch = ')'
Closing → left > 0? Yes → left = 0
State: left=0, right=0
Step 3: ch = ')'
Closing → left > 0? No → right = 1
State: left=0, right=1
Step 4: ch = ')'
Closing → left > 0? No → right = 2
State: left=0, right=2
Step 5: ch = '('
Opening → left = 1
State: left=1, right=2
Step 6: ch = '('
Opening → left = 2
State: left=2, right=2
Final: left + right = 2 + 2 = 4 ✓
Why This Greedy Approach Works
Optimal Substructure
The problem has optimal substructure:
- Making a prefix valid doesn’t affect the optimal solution for the suffix
- We can greedily match parentheses as we go
Mathematical Proof
Claim: The greedy approach (match immediately when possible) gives the minimum additions.
Proof:
- If we have
(and see), matching immediately is optimal- Delaying would require adding a
)later, which is at least as costly
- Delaying would require adding a
- If we see
)with no(to match, we must add a(- Adding it now is as good as adding it later
- Remaining unmatched
(must be closed- No way to avoid adding
)for each remaining(
- No way to avoid adding
Therefore, the greedy approach is optimal.
Visualization of Different Cases
Case 1: Unmatched Closing
s = ")))"
↑
Need to add '(' here (or before)
Additions: 3 '('
Case 2: Unmatched Opening
s = "((("
↑
Need to add ')' here (or after)
Additions: 3 ')'
Case 3: Mixed
s = "()))(("
( ) ) ) ( (
0 1 2 3 4 5
Unmatched closing: positions 2, 3 → need 2 '('
Unmatched opening: positions 4, 5 → need 2 ')'
Additions: 2 '(' + 2 ')' = 4
Optimization Tips
Early Exit (Not Applicable)
We need to process the entire string to count all unmatched parentheses.
Memory Optimization
Already optimal - O(1) space. The counter approach is the most memory-efficient.
Branch Optimization
The ternary operator open > 0 ? open-- : minAdd++ is efficient and clear.
Related Problems
- 20. Valid Parentheses - Check if valid
- 32. Longest Valid Parentheses - Find longest valid substring
- 301. Remove Invalid Parentheses - Remove minimum to make valid
- 1249. Minimum Remove to Make Valid Parentheses - Remove minimum characters
- 1541. Minimum Insertions to Balance a Parentheses String - Similar to this problem
Pattern Recognition
This problem demonstrates the Greedy Counter Pattern:
- Use counter instead of stack for single bracket type
- Track unmatched opening and closing separately
- Greedily match whenever possible
- Sum remaining unmatched counts
Key Insight:
- For single bracket type: counter is simpler and more efficient than stack
- Track two types of deficits separately
- Greedy matching is optimal
Applications:
- Parentheses balancing
- Bracket counting
- Expression validation
- Simple matching problems
Extension: Multiple Bracket Types
If we had multiple bracket types like ()[]{}, we’d need a stack:
def min_add_multi_type(s: str) -> int:
pairs = {")": "(", "]": "[", "}": "{"}
st: list[str] = []
min_add = 0
for c in s:
if c in "([{":
st.append(c)
elif not st:
min_add += 1
elif st[-1] == pairs[c]:
st.pop()
else:
min_add += 1
return min_add + len(st)
But for single bracket type (), the counter approach is optimal.
Code Quality Notes
- Readability: Clear variable names (
open,minAdd) - Efficiency: Optimal O(n) time, O(1) space
- Correctness: Handles all edge cases properly
- Simplicity: Much simpler than stack-based approach for single type
Key Takeaways
- Counter-Based: Use counters instead of stack for single bracket type
- Greedy Matching: Match closing parentheses immediately when possible
- Two Types of Deficits:
- Unmatched closing: Tracked by
right(need to add opening) - Unmatched opening: Tracked by
left(need to add closing)
- Unmatched closing: Tracked by
- Final Answer: Sum of both deficits (
left + right)
References
- LC 921: Minimum Add to Make Parentheses Valid on LeetCode
- LeetCode Discuss — LC 921: Minimum Add to Make Parentheses Valid
- LeetCode Editorial (may require premium)