Minimum Remove to Make Valid Parentheses
Stack indices of unmatched openers; on close-without-opener, mark the closer for removal — then strip.
4 min read
stack string
Problem#
Given a string of lowercase letters and parentheses, remove the minimum number of parentheses to make the parentheses valid. Return any resulting valid string.
Examples#
"lee(t(c)o)de)"→"lee(t(c)o)de""a)b(c)d"→"ab(c)d""))(("→""
Constraints#
1 <= s.length <= 10^5.
Approach#
Walk the string keeping a stack of indices of ( not yet matched. On ), if the stack is non-empty, pop (matched); else mark this index for deletion. At the end, any indices still on the stack are unmatched ( and must also go.
Solution#
class Solution {public: string minRemoveToMakeValid(string s) { stack<int> opens; vector<bool> remove(s.size(), false); for (int i = 0; i < (int)s.size(); ++i) { if (s[i] == '(') opens.push(i); else if (s[i] == ')') { if (!opens.empty()) opens.pop(); else remove[i] = true; } } while (!opens.empty()) { remove[opens.top()] = true; opens.pop(); } string ans; ans.reserve(s.size()); for (int i = 0; i < (int)s.size(); ++i) if (!remove[i]) ans.push_back(s[i]); return ans; }};func minRemoveToMakeValid(s string) string { remove := make([]bool, len(s)) var opens []int for i := 0; i < len(s); i++ { switch s[i] { case '(': opens = append(opens, i) case ')': if len(opens) > 0 { opens = opens[:len(opens)-1] } else { remove[i] = true } } } for _, idx := range opens { remove[idx] = true } b := make([]byte, 0, len(s)) for i := 0; i < len(s); i++ { if !remove[i] { b = append(b, s[i]) } } return string(b)}class Solution: def minRemoveToMakeValid(self, s: str) -> str: remove = [False] * len(s) opens: list[int] = [] for i, ch in enumerate(s): if ch == '(': opens.append(i) elif ch == ')': if opens: opens.pop() else: remove[i] = True for idx in opens: remove[idx] = True return ''.join(ch for i, ch in enumerate(s) if not remove[i])function minRemoveToMakeValid(s) { const remove = new Array(s.length).fill(false); const opens = []; for (let i = 0; i < s.length; i++) { const ch = s[i]; if (ch === '(') opens.push(i); else if (ch === ')') { if (opens.length) opens.pop(); else remove[i] = true; } } for (const idx of opens) remove[idx] = true; let out = ''; for (let i = 0; i < s.length; i++) { if (!remove[i]) out += s[i]; } return out;}class Solution { public String minRemoveToMakeValid(String s) { int n = s.length(); boolean[] remove = new boolean[n]; Deque<Integer> opens = new ArrayDeque<>(); for (int i = 0; i < n; i++) { char ch = s.charAt(i); if (ch == '(') opens.push(i); else if (ch == ')') { if (!opens.isEmpty()) opens.pop(); else remove[i] = true; } } while (!opens.isEmpty()) remove[opens.pop()] = true; StringBuilder sb = new StringBuilder(n); for (int i = 0; i < n; i++) { if (!remove[i]) sb.append(s.charAt(i)); } return sb.toString(); }}function minRemoveToMakeValid(s: string): string { const remove: boolean[] = new Array(s.length).fill(false); const opens: number[] = []; for (let i = 0; i < s.length; i++) { const ch = s[i]; if (ch === '(') opens.push(i); else if (ch === ')') { if (opens.length) opens.pop(); else remove[i] = true; } } for (const idx of opens) remove[idx] = true; let out = ''; for (let i = 0; i < s.length; i++) { if (!remove[i]) out += s[i]; } return out;}Editorial#
Two failure modes: too many closers (unmatched ) left-to-right) and too many openers (unmatched ( remaining at end). The stack catches the first; the leftover stack contents catch the second. We then strip exactly those marked indices.
Complexity#
- Time: O(n).
- Space: O(n).
Concept revision#
Related#