The O(n) Club: Minimum Remove to Make Valid Parentheses — Java’s Answer to Your Bracket Nightmares
⚡ TL;DR
Dev nightmare: You have a string riddled with extra parentheses, and you want the smallest surgery possible to make it valid. The fastest cure? Walk it left-to-right and skip spare
)
, then go right-to-left and dump any lonely(
. All letters survive, only parentheses get evicted. No NP-hard tears. Here’s what not to do:// This will make your CPU weep public String minRemove(String s) { // Brute-force: Try all removals. But unless you’re writing a recursive horror movie, don’t. return "Nope."; }
🧠 How Devs Usually Mess This Up
It’s classic: Devs hack off extra )
on the first pass and forget the poor (
orphans hiding at the end. Or they pile on stack logic like they’re being charged by the paren and wind up juggling indices for sport. News flash: You don’t need to reconstruct some mythical perfect string—any minimal fix is valid. And stacks? That’s overkill for this one, pal.
🔍 What’s Actually Going On
Pretend each (
is trying to swipe into a club, and every )
is the bouncer. Move left-to-right: only openers with chaperones get in. If a closer shows up early, the bouncer (your code) just ignores them. Then you boomerang right-to-left: if you spot an opener whose match already left, just keep walking. Letters? They’re the VIPs—never bounced.
🛠️ PseudoCode
- Create a
StringBuilder
for round one. - Go left-to-right:
- If you hit
(
, increment a balance counter and append it. - If you get
)
and balance > 0, decrement balance and append. If not, skip it. - Non-parentheses? Keep ’em—they never hurt anyone.
- If you hit
- Round two: right-to-left.
- Track “closings” remaining.
- If
)
, increment close and append. - If
(
and close > 0, decrement close and append; otherwise, skip. - Others? Always in the club.
- Reverse the final StringBuilder and you’re out the door.
// First pass: kick out wild ')'
StringBuilder sb = new StringBuilder();
int open = 0;
for (char c : s.toCharArray()) {
if (c == '(') { open++; sb.append(c); }
else if (c == ')') {
if (open > 0) { open--; sb.append(c); } // admit if opener exists
// else, skip
} else { sb.append(c); }
}
// Second pass: kick out lonely '('
StringBuilder res = new StringBuilder();
int close = 0;
for (int i = sb.length() - 1; i >= 0; i--) {
char c = sb.charAt(i);
if (c == ')') { close++; res.append(c); }
else if (c == '(') {
if (close > 0) { close--; res.append(c); }
// else, skip
} else { res.append(c); }
}
return res.reverse().toString();
💻 The Code
public String minRemoveToMakeValid(String s) {
StringBuilder sb = new StringBuilder();
int open = 0;
for (char ch : s.toCharArray()) {
if (ch == '(') {
open++;
sb.append(ch);
} else if (ch == ')') {
if (open > 0) {
open--;
sb.append(ch);
}
} else {
sb.append(ch);
}
}
StringBuilder result = new StringBuilder();
int close = 0;
for (int i = sb.length() - 1; i >= 0; i--) {
char ch = sb.charAt(i);
if (ch == ')') {
close++;
result.append(ch);
} else if (ch == '(') {
if (close > 0) {
close--;
result.append(ch);
}
} else {
result.append(ch);
}
}
return result.reverse().toString();
}
⚠️ Pitfalls, Traps & Runtime Smacks
- If you only remove extra
)
up front, you’ll leave dangling openers. Do both passes or enjoy failing test cases. - Stack approaches teach balancing, but for this you can do even less.
- Leave non-parenthesis chars alone—or the prompt police will find you.
- O(n) time and space—don’t bring this to a zero-copy fight; you’re making buffers both ways.
- Don’t stress about one canonical result—any minimal fix is fair game.
🧠 Interviewer Brain-Tattoo
“String a mess? Two sweeps, no stack, O(n) time—minimal therapy for even maximal bracket chaos. You can quote me, but try not to use this in a regex interview.”