LeetCode #1249 Medium

Minimum Remove to Make Valid Parentheses

Minimum Remove to Make Valid Parentheses: delete the fewest parentheses possible so the result is valid, keeping all letters. Any valid answer is accepted.

Constraints
  • 1 <= s.length <= 10⁵
  • s[i] is either '(' , ')' , or a lowercase English letter.
stringstackgreedy
Open on LeetCode ↗
02

Intuition

Only two kinds of parenthesis can ever be wrong: a ) that arrives with no open partner, and a ( that is still unmatched when the string ends. Find exactly those and delete only them. Every other bracket is part of a legitimate pair, so removing anything else would be more than the minimum.

How to spot this pattern

Balanced-delimiter validity is always a stack question, and the 'minimum deletions' twist just means recording the offenders instead of returning false at the first one. The same identify-the-doomed-then-rebuild shape appears in Remove Invalid Parentheses and Valid Parenthesis String.

03

Approach

Try it first

Before reading on: name the only two situations in which a parenthesis can never be part of a valid pair. Convince yourself that deleting precisely those is both necessary and enough. Aim for O(n).

1

Identify the two failure modes

Scan left to right tracking indices of unmatched ( on a stack. When a ) appears and the stack is non-empty, it pairs with the most recent ( — pop and both are safe. When a ) appears and the stack is empty, nothing can ever match it, because any later ( opens after it. That index is doomed. When the scan finishes, whatever remains on the stack is a set of ( that never found a partner, and those are doomed too. These are the only two ways a string can be invalid.

2

Why deleting exactly those is minimal

Each doomed index must be removed — a stray ) cannot be fixed by deleting anything else, and neither can a stray (. So the doomed set is a lower bound on the number of deletions. It is also sufficient: after removing them, every surviving ( has a matching ) after it and vice versa, so the string is valid. A set that is both necessary and sufficient is exactly the minimum. Letters are never touched, since they cannot affect validity.

3

Rebuild in one pass

Collect the doomed indices into a set, then build the answer by appending every character whose index is not in it. Using a set makes each membership test O(1), so the rebuild is O(n). Total cost is two linear passes and O(n) auxiliary space for the stack and the result. Building a new string is cleaner than repeated deletions, which would be O(n²) because each removal shifts the tail.

04

Solution & live demo

1class Solution:
2 def minRemoveToMakeValid(self, s):
3 chars = list(s)
4 stack = []
5 for i, ch in enumerate(chars):
6 if ch == "(":
7 stack.append(i)
8 elif ch == ")":
9 if stack:
10 stack.pop()
11 else:
12 chars[i] = ""
13 for i in stack:
14 chars[i] = ""
15 return "".join(chars)
05

Common pitfalls

Using a counter but losing positions

✗ Wrong
open_count = 0
for ch in s:
    if ch == '(': open_count += 1
✓ Right
stack.append(i)  # store the index

A counter can tell you how many brackets are unmatched but not which, and the answer requires deleting specific positions. Push indices so the leftovers can be blanked at the end.

Deleting from the string while scanning

✗ Wrong
s = s[:i] + s[i+1:]
✓ Right
chars[i] = ""  # blank it, join later

Every deletion re-indexes the remaining characters, so the loop variable no longer matches the intended position and the scan skips characters. It is also O(n²). Mark first, rebuild once.

Forgetting the leftovers on the stack

✗ Wrong
# handle only the stray ')' cases
return ''.join(chars)
✓ Right
for i in stack:
    chars[i] = ''
return ''.join(chars)

Unmatched ( are only discovered when the scan ends. Skipping that final loop leaves "(a" unchanged and returns an invalid string.

06

Edge cases

Already valid, e.g. "(a)(b)"

The stack empties naturally and no index is marked, so the string is returned unchanged.

Only letters, e.g. "abc"

No parentheses are seen at all; the result equals the input.

Leading stray close, e.g. "))(("

Both ) are marked on arrival and both ( are marked at the end, leaving an empty string.

Nested parentheses, e.g. "((a))"

The stack pairs innermost-first, so all four brackets survive.

Unmatched open at the end, e.g. "(a(b"

Two indices remain on the stack and are removed, leaving "ab".

07

Complexity

Time
O(n)
Space
O(n)
One scan to mark, one to rebuild. The stack holds at most n indices when every character is an open bracket.