LeetCode #1249 Medium

Minimum Remove to Make Valid Parentheses

Minimum Remove to Make Valid Parentheses is LeetCode 1249 (Medium). The string s holds (, ) and lowercase letters. Remove the fewest parentheses so that what is left is valid: every ( is closed by a later ), and every ) closes an earlier (. Letters always stay.

  • Any valid result with the fewest removals is accepted.
  • With up to 100,000 characters, the solution has to run in O(n).
Constraints
  • 1 <= s.length <= 10⁵
  • s[i] is either '(' , ')', or a lowercase English letter.
stringstackgreedy
Open on LeetCode ↗
02

Intuition

Scan left to right and pair each ) with the nearest unmatched ( before it. A stack of the positions of unmatched ( does the pairing: push on (, pop on ).

Only two kinds of bracket can never be paired:

  • A ) that finds the stack empty: every ( before it is taken, and a ( after it cannot close it.
  • A ( still on the stack at the end: no free ) came after it.

Removing exactly those is the minimum. Each one must go, because nothing can pair it, and once they are gone every remaining bracket belongs to a matched pair.

How to spot this pattern

Matching brackets is a stack job. For the fewest deletions, find every bracket that can never match instead of stopping at the first error; the positions left on the stack say exactly which ones to delete.

03

Approach

Try it first

Before reading on, name the only two situations in which a bracket can never be part of a pair. Then decide what you have to store to know, at the end, which opening brackets were never closed. Aim for O(n).

1

Keep a stack of open positions

Walk the string with its index. On (, push the index, because this bracket waits for a later partner. Letters are skipped: they never affect whether the string is valid.

2

Pair or remove each closing bracket

On ), if the stack is not empty, pop it: this ) closes the most recent waiting (. If the stack is empty, mark this ) for removal at once, because nothing before it is free to match it.

3

Remove the opens left over

When the scan ends, every index still on the stack is a ( that no later ) closed. Mark all of them for removal too, since nothing is left in the string that could close them.

4

Build the answer once

Join every character that is not marked. Marking first and building once keeps the work O(n), while deleting characters from the string one by one would shift the rest each time and cost O(n²).

04

Minimum Remove to Make Valid Parentheses solution in Python | C++ | Java

▶1class Solution:
▶2 def minRemoveToMakeValid(self, s):
▶3 chars = list(s)
▶4 open_at = []
▶5 for i, ch in enumerate(chars):
▶6 if ch == "(":
▶7 open_at.append(i)
▶8 elif ch == ")":
▶9 if open_at:
▶10 open_at.pop()
▶11 else:
▶12 chars[i] = ""
▶13 for i in open_at:
▶14 chars[i] = ""
▶15 return "".join(chars)
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scanletter a → keep
i0letter
Letters never affect whether the brackets are valid, so they always stay in the result.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scan) at 1 has nothing to close → remove
removed1
The stack is empty: every ( before this point is already paired. A ( that comes later cannot close a ) before it, so this bracket can never be part of a pair and must be removed.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scanletter b → keep
i2letter
Letters never affect whether the brackets are valid, so they always stay in the result.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at index3resultbuilt after the scan( at 3 waits for a partner
stack[3]
An opening bracket can only be closed by a ) that comes later, so push its index and keep scanning.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at index3resultbuilt after the scanletter c → keep
i4letter
Letters never affect whether the brackets are valid, so they always stay in the result.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scan) at 5 closes ( at 3
stack[]popped 3
The stack top, index 3, is the most recent ( still waiting. This ) closes it, so both are kept and the pair is done.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scanletter d → keep
i6letter
Letters never affect whether the brackets are valid, so they always stay in the result.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultbuilt after the scanno ( left waiting → nothing more to remove
stack[]
The scan is over and the stack is empty, so every ( found a partner. The only removals are the ones marked during the scan.
matched pairremoveda0)1b2(3c4)5d6unmatched ( at indexemptyresultab(c)d1 removed → "ab(c)d"
result"ab(c)d"
Join every character that is not marked. The removed bracket could never be paired, so no valid string uses fewer deletions. Every bracket left belongs to one of the green arcs, so "ab(c)d" is valid.
05

Two counting passes

The first pass keeps a count of open brackets and skips any ) that arrives when the count is 0. Whatever the count is at the end is the number of ( that never closed, so the second pass walks backwards from the end and skips that many (.

▶1class Solution:
▶2 def minRemoveToMakeValid(self, s):
▶3 kept = []
▶4 open_count = 0
▶5 for ch in s:
▶6 if ch == ")":
▶7 if open_count == 0:
▶8 continue
▶9 open_count -= 1
▶10 elif ch == "(":
▶11 open_count += 1
▶12 kept.append(ch)
▶13 
▶14 result = []
▶15 for ch in reversed(kept):
▶16 if ch == "(" and open_count > 0:
▶17 open_count -= 1
▶18 continue
▶19 result.append(ch)
▶20 return "".join(reversed(result))
06

Common pitfalls

Forgetting the opens left on the stack

✗ Wrong
for i, ch in enumerate(chars):
    ...
return "".join(chars)
✓ Right
for i, ch in enumerate(chars):
    ...
for i in open_at:
    chars[i] = ""
return "".join(chars)

An unclosed ( is only known once the scan ends. Without the final loop, "(a" comes back unchanged, and it is not valid; the answer is "a".

Popping without checking the stack

✗ Wrong
elif ch == ")":
    open_at.pop()
✓ Right
elif ch == ")":
    if open_at:
        open_at.pop()
    else:
        chars[i] = ""

A ) with no open partner meets an empty stack. For "a)", Python raises an error and C++ reads past the end of the vector; the answer should be "a".

Dropping the first extra opens instead of the last

✗ Wrong
for ch in kept:
    if ch == "(" and open_count > 0:
        open_count -= 1
        continue
    result.append(ch)
✓ Right
for ch in reversed(kept):
    if ch == "(" and open_count > 0:
        open_count -= 1
        continue
    result.append(ch)
result.reverse()

In the counting version, the ( that never close are always the rightmost ones. For "(()(" two ( are extra: dropping the first two leaves ")(", which is invalid, while dropping the last two leaves "()".

07

Edge cases

Every bracket is unmatched

For "))((", both ) are removed during the scan and both ( at the end, so the answer is the empty string, which counts as valid.

Your answer differs from the expected one

Several results can tie. For "lee(t(c)o)de)", "lee(t(co)de)" and "lee(t(c)ode)" are also accepted; the stack method happens to return "lee(t(c)o)de".

08

Complexity

Time
O(n)
Space
O(n)
One pass to pair and mark, one pass over the leftover indices and one pass to build the result. The stack holds at most n indices, when every character is (, and the result is up to n characters.
09

Parentheses problems that look alike

Four LeetCode problems share the same strings but ask for different things:

ProblemAsks forMethod
20 Valid Parenthesesis the string already valid?stack, stop at the first mismatch
921 Minimum Add to Make Parentheses Validhow many brackets to insertcount unmatched ) and leftover (
1249 Minimum Remove (this page)one valid string with the fewest removalsstack of positions, delete the unmatched
301 Remove Invalid Parenthesesevery distinct valid string with the fewest removalsBFS or backtracking over removals (Hard)