- Link: 1249. Minimum Remove to Make Valid Parentheses - Medium
- Track: NeetCode150
Question
Restate the problem
- Given a string
swith letters and parentheses, remove the minimum number of parentheses to make it valid (every(has a matching)and vice versa). - Return any valid result.
Edge Case
- No parentheses → return as-is
- All parentheses, no letters → may return
"" - Nested:
"((("→ remove all
| Method | Approach | Time Complexity | Space Complexity |
|---|---|---|---|
| Method 1 ⭐ | Stack + Set | ||
| Method 2 | Two-pass balance |
Method 1 - Stack + Set
Key
The stack only ever holds
(indices, so it naturally catches both problems:
- stack is empty when
)arrives → unmatched);- indices still in stack after full scan → unmatched
(.
Approach
- Use a stack to track indices of unmatched
(. Use a set to track indices to remove.- Scan left to right
(→ push its index onto stack)→ if stack has a match, pop it (valid pair found); else mark this)index for removal
- After the scan, any indices still in the stack are unmatched
(→ add them to remove set - Rebuild the string, skipping indices in the remove set
- Scan left to right
Complexity
- Time Complexity:
- Space Complexity:
Code
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
stack = [] # only store left parentheses
remove = set()
for i, char in enumerate(s):
if char == "(":
stack.append(i)
if char == ")":
if stack:
stack.pop()
else:
remove.add(i)
remove.update(stack)
res = []
for i, char in enumerate(s):
if i not in remove:
res.append(char)
return "".join(res)Method 2 - Two-pass balance
Approach
-
Two-pass without a stack. Use a
balancecounter instead of tracking indices.- Pass 1 (left → right): remove invalid
)— any)whenbalance == 0has no matching(, skip it. - Pass 2 (right → left): remove invalid
(— any(whenright_balance == 0has no matching), skip it.
- Pass 1 (left → right): remove invalid
-
Key insight: after pass 1, all
)are valid. But some(may still be unmatched — pass 2 cleans those up by scanning backwards.
Complexity
- Time Complexity:
- Space Complexity:
Code
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
res = []
balance = 0
# first pass: remove invalid ')'
for c in s:
if c not in "()":
res.append(c)
elif c == "(":
balance += 1
res.append(c)
else: # c == ")"
if balance > 0:
balance -= 1
res.append(c)
ans = []
right_balance = 0
# second pass: remove invalid '('
for i in range(len(res) - 1, -1, -1):
c = res[i]
if c not in "()":
ans.append(c)
elif c == ")":
right_balance += 1
ans.append(c)
else: # c == "("
if right_balance > 0:
right_balance -= 1
ans.append(c)
ans.reverse()
return "".join(ans)