[LeetCode] 301. Remove Invalid Parentheses 移除非法括号
Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.
Note: The input string may contain letters other than the parentheses (
and )
.
Example 1:
Input: "()())()" Output: ["()()()", "(())()"]
Example 2:
Input: "(a)())()" Output: ["(a)()()", "(a())()"]
Example 3:
Input: ")(" Output: [""]
给一个字符串,里面含有'(', ')'和字母,其中的小括号可能不配对,移除不配对的括号,使得括号匹配,返回所有可能的解。
判断括号是否合法,可以用栈,这也是栈的一个典型应用。也可用一个count计数器,遇到左括号++,右括号--,一旦count小于0,就说明不合法。比较推荐count方式,空间复杂度较低。
解法1: BFS: 枚举去除的点,当找到后停止BFS树的扩展(因为要去除最少括号,所以即使有其他的结果,也一定在同一层)
解法2: DFS: 统计左右括号能删的个数,进行DFS。
Followup: F家,只需要返回最少删除括号的个数
Java:
Key Points:
Generate unique answer once and only once, do not rely on Set.
Do not need preprocess.
Runtime 3 ms.
Explanation:
We all know how to check a string of parentheses is valid using a stack. Or even simpler use a counter.
The counter will increase when it is ‘(‘ and decrease when it is ‘)’. Whenever the counter is negative, we have more ‘)’ than ‘(‘ in the prefix.
To make the prefix valid, we need to remove a ‘)’. The problem is: which one? The answer is any one in the prefix. However, if we remove any one, we will generate duplicate results, for example: s = ()), we can remove s[1] or s[2] but the result is the same (). Thus, we restrict ourself to remove the first ) in a series of concecutive )s.
After the removal, the prefix is then valid. We then call the function recursively to solve the rest of the string. However, we need to keep another information: the last removal position. If we do not have this position, we will generate duplicate by removing two ‘)’ in two steps only with a different order.
For this, we keep tracking the last removal position and only remove ‘)’ after that.
Now one may ask. What about ‘(‘? What if s = ‘(()(()’ in which we need remove ‘(‘?
The answer is: do the same from right to left.
However a cleverer idea is: reverse the string and reuse the code!
Here is the final implement in Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public List<String> removeInvalidParentheses(String s) { List<String> ans = new ArrayList<>(); remove(s, ans, 0 , 0 , new char []{ '(' , ')' }); return ans; } public void remove(String s, List<String> ans, int last_i, int last_j, char [] par) { for ( int stack = 0 , i = last_i; i < s.length(); ++i) { if (s.charAt(i) == par[ 0 ]) stack++; if (s.charAt(i) == par[ 1 ]) stack--; if (stack >= 0 ) continue ; for ( int j = last_j; j <= i; ++j) if (s.charAt(j) == par[ 1 ] && (j == last_j || s.charAt(j - 1 ) != par[ 1 ])) remove(s.substring( 0 , j) + s.substring(j + 1 , s.length()), ans, i, j, par); return ; } String reversed = new StringBuilder(s).reverse().toString(); if (par[ 0 ] == '(' ) // finished left to right remove(reversed, ans, 0 , 0 , new char []{ ')' , '(' }); else // finished right to left ans.add(reversed); } |
Java: DFS
Calculate the number of invalid parentheses of the original string. Iterate through the string. Remove each character and DFS if the number of invalid parentheses decreases.
This solution is based on the fact that if we're on the right path to the optimal string, the number of invalid parentheses must always decrease.
Time complexity:
In the worst case, I could have some input like "))))))))", where I need to search through the entire string. The good thing is duplicates will be pruned by the hash set. Calculating mis-match takes O(n). So the overall time complexity is O(n^2).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class Solution { public List<String> removeInvalidParentheses(String s) { List<String> list = new ArrayList<>(); Set<String> visited = new HashSet<>(); removeInvalidParentheses(s, numberOfInvalid(s), list, visited); return list; } private void removeInvalidParentheses(String s, int invalid, List<String> list, Set<String> visited) { if (invalid == 0 ) { list.add(s); return ; } for ( int i = 0 ; i < s.length(); i++) { if (s.charAt(i) != '(' && s.charAt(i) != ')' ) { continue ; } String child = s.substring( 0 , i) + s.substring(i + 1 , s.length()); if (!visited.contains(child)) { visited.add(child); int next = numberOfInvalid(child); if (next < invalid) { removeInvalidParentheses(child, next, list, visited); } } } } private int numberOfInvalid(String s) { int open = 0 ; int close = 0 ; for ( char c : s.toCharArray()) { if (c == '(' ) { open++; } else if (c == ')' ) { if (open == 0 ) { close++; } else { open--; } } } return open + close; } } |
Java: BFS
Thought process:
BFS:
Graph definition:
Vertex: a candidate string.
Edge: two strings s1 and s2 have an edge if s1 equals s2 with one parenthesis deleted.
Put string into a queue.
For the current size of the queue, poll a string from the queue.
Iterate through the string. For each character, remove it, and check if the parentheses are valid.
If so, iterate over current level and return the result.
If not, offer the new string to the queue.
Time complexity:
Say the string's length is n. For every character, the choice is to keep or remove. So there are 2^n total states to check. Check if a string is valid is O(1). So the overall time complexity is O(2^n).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | class Solution { public List<String> removeInvalidParentheses(String s) { List<String> list = new ArrayList<>(); Queue<String> queue = new LinkedList<>(); queue.offer(s); Set<String> visited = new HashSet<>(); visited.add(s); boolean found = false ; while (!found && !queue.isEmpty()) { int size = queue.size(); for ( int i = 0 ; i < size; i++) { String str = queue.poll(); if (isValid(str)) { list.add(str); found = true ; continue ; } for ( int j = 0 ; j < str.length(); j++) { if (str.charAt(j) != '(' && str.charAt(j) != ')' ) { continue ; } String child = str.substring( 0 , j) + str.substring(j + 1 ); if (!visited.contains(child)) { queue.offer(child); visited.add(child); } } } } return list; } private boolean isValid(String s) { int open = 0 ; for ( char c : s.toCharArray()) { if (c == '(' ) { open++; } else if (c == ')' ) { if (open == 0 ) { return false ; } open--; } } return open == 0 ; } } |
Python: DFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Solution( object ): def removeInvalidParentheses( self , s): """ :type s: str :rtype: List[str] """ def dfs(s): mi = calc(s) if mi = = 0 : return [s] ans = [] for x in range ( len (s)): if s[x] in ( '(' , ')' ): ns = s[:x] + s[x + 1 :] if ns not in visited and calc(ns) < mi: visited.add(ns) ans.extend(dfs(ns)) return ans def calc(s): a = b = 0 for c in s: a + = { '(' : 1 , ')' : - 1 }.get(c, 0 ) b + = a < 0 a = max (a, 0 ) return a + b visited = set ([s]) return dfs(s) |
Python: BFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class Solution( object ): def removeInvalidParentheses( self , s): """ :type s: str :rtype: List[str] """ def calc(s): a = b = 0 for c in s: a + = { '(' : 1 , ')' : - 1 }.get(c, 0 ) b + = a < 0 a = max (a, 0 ) return a + b visited = set ([s]) ans = [] queue = collections.deque([s]) done = False while queue: t = queue.popleft() mi = calc(t) if mi = = 0 : done = True ans.append(t) if done: continue for x in range ( len (t)): if t[x] not in ( '(' , ')' ): continue ns = t[:x] + t[x + 1 :] if ns not in visited and calc(ns) < mi: visited.add(ns) queue.append(ns) return ans |
Python: DFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class Solution( object ): def removeInvalidParentheses( self , s): """ :type s: str :rtype: List[str] """ if not s: return [''] left_remove = right_remove = 0 for c in s: if c = = '(' : left_remove + = 1 elif c = = ')' : if left_remove: left_remove - = 1 else : right_remove + = 1 ans = set () self .dfs( 0 , left_remove, right_remove, 0 , '', s, ans) return list (ans) def dfs( self , index, left_remove, right_remove, left_pare, cur, s, ans): if left_remove < 0 or right_remove < 0 or left_pare < 0 : return if index = = len (s): if left_remove = = right_remove = = left_pare = = 0 : ans.add(cur) return if s[index] = = '(' : self .dfs(index + 1 , left_remove - 1 , right_remove, left_pare, cur, s, ans) self .dfs(index + 1 , left_remove, right_remove, left_pare + 1 , cur + s[index], s, ans) elif s[index] = = ')' : self .dfs(index + 1 , left_remove, right_remove - 1 , left_pare, cur, s, ans) self .dfs(index + 1 , left_remove, right_remove, left_pare - 1 , cur + s[index], s, ans) else : self .dfs(index + 1 , left_remove, right_remove, left_pare, cur + s[index], s, ans) |
Python: BFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Solution( object ): def removeInvalidParentheses( self , s): """ :type s: str :rtype: List[str] """ if not s: return [''] q, ans, vis = [s], [], set ([s]) found = False while q: cur = q.pop( 0 ) if self .isValidParentheses(cur): found = True ans.append(cur) elif not found: for i in xrange ( len (cur)): if cur[i] = = '(' or cur[i] = = ')' : t = cur[:i] + cur[i + 1 :] if t not in vis: q.append(t) vis.add(t) return ans def isValidParentheses( self , s): cnt = 0 for c in s: if c = = '(' : cnt + = 1 elif c = = ')' : if cnt = = 0 : return False cnt - = 1 return cnt = = 0 |
Python: BFS, concise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Solution( object ): def removeInvalidParentheses( self , s): """ :type s: str :rtype: List[str] """ if not s: return [''] q = {s} while q: ans = filter ( self .isValidParentheses,q) if ans: return ans q = {cur[:i] + cur[i + 1 :] for cur in q for i in xrange ( len (cur))} def isValidParentheses( self , s): cnt = 0 for c in s: if c = = '(' : cnt + = 1 elif c = = ')' : if cnt = = 0 : return False cnt - = 1 return cnt = = 0 |
C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class Solution { public : vector<string> removeInvalidParentheses(string s) { vector<string> res; unordered_set<string> visited{{s}}; queue<string> q{{s}}; bool found = false ; while (!q.empty()) { string t = q.front(); q.pop(); if (isValid(t)) { res.push_back(t); found = true ; } if (found) continue ; for ( int i = 0; i < t.size(); ++i) { if (t[i] != '(' && t[i] != ')' ) continue ; string str = t.substr(0, i) + t.substr(i + 1); if (!visited.count(str)) { q.push(str); visited.insert(str); } } } return res; } bool isValid(string t) { int cnt = 0; for ( int i = 0; i < t.size(); ++i) { if (t[i] == '(' ) ++cnt; else if (t[i] == ')' && --cnt < 0) return false ; } return cnt == 0; } }; |
All LeetCode Questions List 题目汇总
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构