1541. Minimum Insertions to Balance a Parentheses String
Problem Description
This problem asks you to balance a special type of parentheses string where the matching rule is different from standard parentheses matching.
Special Matching Rules:
- Each opening parenthesis
'('
must be matched with two consecutive closing parentheses'))'
- The opening parenthesis
'('
must appear before its corresponding'))'
- Think of
'('
as opening and'))'
as closing (not single')'
)
Valid vs Invalid Examples:
- Valid:
"())"
,"())(()))))"
,"(())()))))"
- Invalid:
")()"
,"()))"
,"(()))"
Task:
Given a string s
containing only '('
and ')'
characters, you can insert '('
or ')'
characters at any position to make the string balanced according to the rules above. Find the minimum number of insertions needed.
Key Observations:
- If you encounter a single
')'
that isn't followed by another')'
, you need to insert one')'
to make it a valid closing pair - If you encounter
'))'
but have no unmatched'('
to pair with, you need to insert a'('
- Any leftover unmatched
'('
at the end needs two')'
characters to be inserted
Example Walkthrough:
For string "(()))"
:
- First
'('
needs'))'
→ found at positions 2-3 ✓ - Second
'('
needs'))'
→ only one')'
at position 4 - Need to insert 1 more
')'
to complete the pair - Total insertions: 1
Intuition
The key insight is to process the string from left to right while keeping track of unmatched opening parentheses, similar to standard parentheses matching but with a twist for the double closing requirement.
Why a single-pass greedy approach works:
When we encounter characters sequentially, we face only a few scenarios:
- We see a
'('
→ This will need a future'))'
to match, so we track it - We see a
')'
→ We need to check if the next character is also')'
to form a valid closing pair
The greedy decision happens when we see a ')'
:
- If the next character is also
')'
, we have a complete closing pair'))'
- If not, we immediately insert a
')'
to complete the pair (this is optimal because delaying won't reduce insertions)
Why track unmatched '('
count:
Using a counter x
for unmatched opening parentheses helps us determine:
- When we encounter
'))'
, ifx > 0
, we can match it with a previous'('
and decrementx
- If
x = 0
when we see'))'
, we must insert a'('
before it to create a valid pair
Why multiply remaining count by 2:
After processing the entire string, if x > 0
, it means we have x
unmatched '('
characters. Each needs exactly two ')'
characters to complete its pair, hence we add x * 2
(or x << 1
) insertions.
The beauty of this approach:
We make locally optimal decisions (insert immediately when needed) that lead to a globally optimal solution. We never need to backtrack or reconsider previous decisions because:
- Inserting a
')'
to complete a pair is always necessary when we don't have the next')'
- Inserting a
'('
when we have no unmatched ones is always necessary for a'))'
pair - The order of processing naturally handles nested and sequential patterns correctly
Solution Approach
The implementation uses a single-pass iteration with a counter to track unmatched opening parentheses. Here's how the algorithm works:
Variables Used:
ans
: Counts the total number of insertions neededx
: Tracks the number of unmatched'('
parentheses waiting for their'))'
pairsi
: Index pointer to traverse the stringn
: Length of the string
Step-by-step Algorithm:
-
Initialize counters: Set
ans = 0
(insertions),x = 0
(unmatched opens),i = 0
(index) -
Main loop - Process each character while
i < n
:Case 1: Opening parenthesis
'('
- Simply increment
x += 1
to track one more unmatched opening - Move to next character
Case 2: Closing parenthesis
')'
- First, check if we can form a complete closing pair
'))'
:- If
i < n - 1
ands[i + 1] == ')'
, we have'))'
→ advancei
by 1 extra - Otherwise, we only have single
')'
→ insert one')'
by incrementingans += 1
- If
- Next, check if we have an opening to match with:
- If
x == 0
(no unmatched openings), insert a'('
by incrementingans += 1
- If
x > 0
(have unmatched openings), match it by decrementingx -= 1
- If
- Move to next character with
i += 1
- Simply increment
-
Post-processing: After the loop, if
x > 0
, we have unmatched opening parentheses- Each needs two
')'
to complete, so addans += x << 1
(equivalent tox * 2
)
- Each needs two
Example Trace for "(()))"
:
i=0
: See'('
→x = 1
i=1
: See'('
→x = 2
i=2
: See')'
, next is')'
→ found'))'
,x > 0
sox = 1
, advancei
to 3i=4
: See')'
, no next char → insert one')'
(ans = 1
),x > 0
sox = 0
- End:
x = 0
, no additional insertions needed - Result:
ans = 1
The algorithm efficiently handles all cases in O(n)
time with O(1)
space, making optimal insertion decisions on the fly.
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 trace through the algorithm with the string "())("
:
Initial State:
- String:
"())("
(length n = 4) ans = 0
(insertions needed)x = 0
(unmatched opening parentheses)i = 0
(current index)
Step 1 (i = 0): Character is '('
- This is an opening parenthesis, so increment
x = 1
- Move to next:
i = 1
Step 2 (i = 1): Character is ')'
- Check if next character forms
'))
: s[2] =')'
✓ - We have a complete
'))'
pair, advance extra:i = 2
- Check if we can match with an opening:
x = 1 > 0
✓ - Match it:
x = 0
- Move to next:
i = 3
Step 3 (i = 3): Character is '('
- This is an opening parenthesis, so increment
x = 1
- Move to next:
i = 4
Step 4: Loop ends (i = 4 ≥ n)
Post-processing:
- We have
x = 1
unmatched opening parenthesis - Each needs two
')'
to complete:ans += 1 × 2 = 2
Final Result: ans = 2
The algorithm correctly identifies that we need to insert 2 characters total:
- The string
"())("
becomes"())())"
after inserting two')'
at the end - First
'('
matches with'))'
at positions 1-2 ✓ - Second
'('
matches with the inserted'))'
✓
Solution Implementation
1class Solution:
2 def minInsertions(self, s: str) -> int:
3 # Track the number of insertions needed
4 insertions_needed = 0
5 # Track the number of unmatched left parentheses
6 unmatched_left_parens = 0
7
8 # Index for traversing the string
9 index = 0
10 string_length = len(s)
11
12 while index < string_length:
13 if s[index] == '(':
14 # Found a left parenthesis, increment unmatched count
15 unmatched_left_parens += 1
16 else:
17 # Found a right parenthesis, check if there's another one following
18 if index < string_length - 1 and s[index + 1] == ')':
19 # Found two consecutive right parentheses, move index forward
20 index += 1
21 else:
22 # Only one right parenthesis, need to insert another one
23 insertions_needed += 1
24
25 # Now we have a pair of right parentheses (either found or inserted)
26 if unmatched_left_parens == 0:
27 # No left parenthesis to match with, need to insert one
28 insertions_needed += 1
29 else:
30 # Match with an existing left parenthesis
31 unmatched_left_parens -= 1
32
33 # Move to the next character
34 index += 1
35
36 # After traversing, if there are still unmatched left parentheses,
37 # each needs two right parentheses inserted (hence multiply by 2)
38 insertions_needed += unmatched_left_parens * 2
39
40 return insertions_needed
41
1class Solution {
2 public int minInsertions(String s) {
3 int insertionsNeeded = 0; // Count of characters we need to insert
4 int openParentheses = 0; // Count of unmatched opening parentheses
5 int length = s.length();
6
7 for (int i = 0; i < length; i++) {
8 char currentChar = s.charAt(i);
9
10 if (currentChar == '(') {
11 // Found an opening parenthesis, increment counter
12 openParentheses++;
13 } else {
14 // Found a closing parenthesis ')'
15
16 // Check if we have a pair of closing parentheses '))'
17 if (i < length - 1 && s.charAt(i + 1) == ')') {
18 // We have '))', skip the next character
19 i++;
20 } else {
21 // We only have single ')', need to insert one more ')'
22 insertionsNeeded++;
23 }
24
25 // Now we have a complete '))', check if there's a matching '('
26 if (openParentheses == 0) {
27 // No opening parenthesis available, need to insert one '('
28 insertionsNeeded++;
29 } else {
30 // Match this '))' with an available '('
31 openParentheses--;
32 }
33 }
34 }
35
36 // Each remaining opening parenthesis needs two closing parentheses
37 insertionsNeeded += openParentheses * 2;
38
39 return insertionsNeeded;
40 }
41}
42
1class Solution {
2public:
3 int minInsertions(string s) {
4 int insertionsNeeded = 0; // Count of characters we need to insert
5 int openParentheses = 0; // Count of unmatched opening parentheses
6 int n = s.size();
7
8 for (int i = 0; i < n; ++i) {
9 if (s[i] == '(') {
10 // Found an opening parenthesis, increment the counter
11 ++openParentheses;
12 } else {
13 // Found a closing parenthesis, need to check if we have a pair '))'
14
15 // Check if the next character is also a closing parenthesis
16 if (i < n - 1 && s[i + 1] == ')') {
17 // We have a complete pair '))', skip the next character
18 ++i;
19 } else {
20 // We only have a single ')', need to insert another ')'
21 ++insertionsNeeded;
22 }
23
24 // Now we have a complete '))', check if there's a matching '('
25 if (openParentheses == 0) {
26 // No opening parenthesis to match, need to insert a '('
27 ++insertionsNeeded;
28 } else {
29 // Match this '))' with an opening parenthesis
30 --openParentheses;
31 }
32 }
33 }
34
35 // Each remaining opening parenthesis needs two closing parentheses
36 insertionsNeeded += openParentheses * 2;
37
38 return insertionsNeeded;
39 }
40};
41
1function minInsertions(s: string): number {
2 // Count of characters we need to insert to make the string valid
3 let insertionsNeeded: number = 0;
4 // Count of unmatched opening parentheses '('
5 let openParentheses: number = 0;
6 // Length of the input string
7 const n: number = s.length;
8
9 // Iterate through each character in the string
10 for (let i = 0; i < n; i++) {
11 if (s[i] === '(') {
12 // Found an opening parenthesis, increment the counter
13 openParentheses++;
14 } else {
15 // Found a closing parenthesis ')'
16 // Need to check if we have a complete pair '))'
17
18 // Check if the next character is also a closing parenthesis
19 if (i < n - 1 && s[i + 1] === ')') {
20 // We have a complete pair '))', skip the next character
21 i++;
22 } else {
23 // We only have a single ')', need to insert another ')' to form '))'
24 insertionsNeeded++;
25 }
26
27 // Now we have a complete '))', check if there's a matching '(' before it
28 if (openParentheses === 0) {
29 // No opening parenthesis to match with this '))', need to insert a '('
30 insertionsNeeded++;
31 } else {
32 // Match this '))' with an existing opening parenthesis
33 openParentheses--;
34 }
35 }
36 }
37
38 // Each remaining unmatched opening parenthesis needs two closing parentheses '))'
39 insertionsNeeded += openParentheses * 2;
40
41 return insertionsNeeded;
42}
43
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
. The algorithm uses a while loop that iterates through each character of the string exactly once. Although there's a special case where we check s[i + 1]
and increment i
by an additional step, each character is still visited at most once. All operations inside the loop (comparisons, arithmetic operations, and variable assignments) take constant time O(1)
. Therefore, the overall time complexity is linear.
Space Complexity: O(1)
. The algorithm only uses a fixed number of variables (ans
, x
, i
, and n
) regardless of the input size. No additional data structures like arrays, lists, or stacks are created that grow with the input size. The space usage remains constant throughout the execution of the algorithm.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting to Skip the Second ')' in a Valid Pair
The Problem:
When encountering two consecutive closing parentheses '))
, many implementations forget to skip the second ')'
after processing the pair. This leads to processing the same ')'
twice, resulting in incorrect counts.
Incorrect Implementation:
# WRONG: Doesn't skip the second ')' in '))'
if s[index] == ')':
if index < len(s) - 1 and s[index + 1] == ')':
# Found '))', but index is NOT incremented here
pass # Process the pair
else:
insertions_needed += 1
# ... rest of logic
index += 1 # Only increments by 1, will process second ')' again!
Correct Solution:
if s[index] == ')':
if index < len(s) - 1 and s[index + 1] == ')':
index += 1 # Skip the second ')' immediately
else:
insertions_needed += 1
# ... rest of logic
index += 1 # Normal increment
Pitfall 2: Incorrect Final Count for Unmatched Opening Parentheses
The Problem:
Each unmatched '('
requires two ')'
characters to be inserted, not one. A common mistake is adding only the count of unmatched openings instead of doubling it.
Incorrect Implementation:
# WRONG: Only adds one ')' per unmatched '(' insertions_needed += unmatched_left_parens # Should be multiplied by 2!
Correct Solution:
# Each unmatched '(' needs TWO ')' characters insertions_needed += unmatched_left_parens * 2
Pitfall 3: Processing Characters Instead of Logical Units
The Problem:
Treating each ')'
independently rather than recognizing '))'
as a single closing unit leads to complex and error-prone logic.
Incorrect Approach:
# WRONG: Processing each ')' separately makes logic complicated for char in s: if char == '(': stack.append('(') elif char == ')': # Complex logic to track if this is first or second ')' # Need additional state variables to track partial matches
Correct Solution:
Use lookahead to identify '))'
as a complete unit:
while index < len(s):
if s[index] == ')':
# Check ahead for complete '))'
if index < len(s) - 1 and s[index + 1] == ')':
# Process as complete unit
index += 1 # Skip second ')'
Example Where These Pitfalls Cause Issues:
For string "()))"
:
- With Pitfall 1: Would process the second
')'
at index 2 twice, incorrectly calculating insertions - With Pitfall 2: Would return 0 instead of the correct answer if there were unmatched
'('
at the end - With Pitfall 3: Would struggle to correctly identify that positions 1-2 form a valid
'))'
pair
These pitfalls highlight the importance of:
- Properly managing the index when identifying two-character patterns
- Understanding the matching rules (1 opening needs 2 closings)
- Thinking in terms of logical units rather than individual characters
What are the most two important steps in writing a depth first search function? (Select 2)
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!