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 bothA
andB
are valid) - It can be formed by wrapping a valid string with parentheses (like
(A)
whereA
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
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:
- When we see
'('
, we're creating a "debt" that needs to be paid with a future')'
- 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.
Solution Approach
The solution implements a greedy stack-based approach to count unmatched parentheses.
Algorithm Steps:
-
Initialize an empty stack - This will store all unmatched parentheses as we process the string.
-
Iterate through each character
c
in the strings
:- 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)
- Pop the
- Otherwise, this
')'
is unmatched:- Push
')'
onto the stack
- Push
- Check if the stack is not empty AND the top element is an opening parenthesis
- If
c
is an opening parenthesis'('
:- Always push it onto the stack (it's waiting for a future match)
- If
-
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 EvaluatorExample 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.
What's the relationship between a tree and a graph?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!