[LeetCode] Remove Invalid Parentheses 移除非法括号

 

Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.html

Note: The input string may contain letters other than the parentheses ( and ).java

Example 1:数组

Input: "()())()"
Output: ["()()()", "(())()"]

Example 2:函数

Input: "(a)())()"
Output: ["(a)()()", "(a())()"]

Example 3:post

Input: ")("
Output: [""]

Credits:
Special thanks to @hpplayer for adding this problem and creating all test cases.this

Subscribe to see which companies asked this questionurl

 
这道题让咱们移除最少的括号使得给定字符串为一个合法的含有括号的字符串,咱们从小数学里就有括号,因此应该对合法的含有括号的字符串并不陌生,字符串中的左右括号数应该相同,并且每一个右括号左边必定有其对应的左括号,并且题目中给的例子也说明了去除方法不惟一,咱们须要找出全部合法的取法。参考了网上大神的解法,这道题首先能够用BFS来解,咱们先把给定字符串排入队中,而后取出检测其是否合法,若合法直接返回,不合法的话,咱们对其进行遍历,对于遇到的左右括号的字符,咱们去掉括号字符生成一个新的字符串,若是这个字符串以前没有遇到过,将其排入队中,咱们用哈希集合记录一个字符串是否出现过。咱们对队列中的每一个元素都进行相同的操做,直到队列为空还没找到合法的字符串的话,那就返回空集,参见代码以下:

 

解法一:spa

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;
    }
};

 

下面来看一种递归解法,这种解法首先统计了多余的半括号的数量,用cnt1表示多余的左括号,cnt2表示多余的右括号,由于给定字符串左右括号要么同样多,要么左括号多,要么右括号多,也可能左右括号都多,好比")("。因此cnt1和cnt2要么都为0,要么都大于0,要么一个为0,另外一个大于0。好,下面进入咱们的递归函数,首先判断,若是当cnt1和cnt2都为0时,说明此时左右括号个数相等了,咱们调用isValid子函数来判断是否正确,正确的话加入结果res中并返回便可。不然从start开始遍历,这里的变量start表示当前递归开始的位置,咱们不须要每次都从头开始,会有大量重复计算。并且对于多个相同的半括号在一块儿,咱们只删除第一个,好比"())",这里有两个右括号,咱们无论删第一个仍是删第二个右括号都会获得"()",没有区别,因此只用算一次就好了,咱们经过和上一个字符比较,若是不相同,说明是第一个右括号,若是相同则直接跳过。此时来看若是cnt1大于0,说明此时左括号多,而若是当前字符正好是左括号的时候,咱们能够删掉当前左括号,继续调用递归,此时cnt1的值就应该减1,由于已经删掉了一个左括号。同理,若是cnt2大于0,说明此时右括号多,而若是当前字符正好是右括号的时候,咱们能够删掉当前右括号,继续调用递归,此时cnt2的值就应该减1,由于已经删掉了一个右括号。参见代码以下:code

 

解法二:htm

class Solution {
public:
    vector<string> removeInvalidParentheses(string s) {
        vector<string> res;
        int cnt1 = 0, cnt2 = 0;
        for (char c : s) {
            cnt1 += (c == '(');
            if (cnt1 == 0) cnt2 += (c == ')');
            else cnt1 -= (c == ')');
        }
        helper(s, 0, cnt1, cnt2, res);
        return res;
    }
    void helper(string s, int start, int cnt1, int cnt2, vector<string>& res) {
        if (cnt1 == 0 && cnt2 == 0) {
            if (isValid(s)) res.push_back(s);
            return;
        }
        for (int i = start; i < s.size(); ++i) {
            if (i != start && s[i] == s[i - 1]) continue;
            if (cnt1 > 0 && s[i] == '(') {
                helper(s.substr(0, i) + s.substr(i + 1), i, cnt1 - 1, cnt2, res);
            }
            if (cnt2 > 0 && s[i] == ')') {
                helper(s.substr(0, i) + s.substr(i + 1), i, cnt1, cnt2 - 1, 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;
    }
};

 

下面这种解法是论坛上的高票解法,思路确实很巧妙。递归函数的参数中,last_i表示当前遍历到的位置,至关上面解法中的start,last_j表示上一个删除的位置,这样能够避免重复计算。而后有个括号字符数组,初始化时放入左括号和右括号,博主认为这个字符数组是此解法最精髓的地方,由于其顺序能够改变,能够变成反向括号,这个就比较叼了,后面再讲它到底有多叼吧。咱们在递归函数中,从last_i开始遍历,在找正向括号的时候,用变量cnt表示括号数组中的左括号出现的次数,遇到左括号自增1,遇到右括号自减1。当左括号大于等于右括号的时候,咱们直接跳过。这个循环的目的是要删除多余的右括号,因此当cnt小于0的时候,咱们从上一个删除位置last_j开始遍历,若是当前是右括号,且是第一个右括号(关于这块能够参见上面解法中的分析),咱们删除当前右括号,并调用递归函数。注意这个for循环结束后要直接返回,由于进这个for循环的都是右括号多的,删到最后最可能是删成和左括号同样多,不须要再去翻转删左括号。好,最后来讲这个最叼的翻转,当字符串的左括号个数大于等于右括号的时候,不会进入第二个for循环,天然也不会return。那么因为左括号的个数可能会要大于右括号,因此咱们还要删除多余的左括号,因此咱们将字符串反转一下,好比"(()",反转变成")((",此时虽然咱们仍是要删除多余的左括号,可是反转后就没有合法的括号了,因此变成了找反向括号")(",那么仍是能够删除多余的左括号,而后咱们判断此时括号数组的状态,若是是正向括号,说明此时正要删除左括号,那么就调用递归函数,last_i和last_j均重置为0,括号数组初始化为反向括号。若是此时已是反向括号了,说明以前的左括号已经删掉了变成了")(",而后又反转了一下,变回来了"()",那么就能够直接加入结果res了,参见代码以下:

 

解法三:

class Solution {
public:
    vector<string> removeInvalidParentheses(string s) {
        vector<string> res;
        helper(s, 0, 0, {'(', ')'}, res);
        return res;
    }
    void helper(string s, int last_i, int last_j, vector<char> p, vector<string>& res) {
        int cnt = 0;
        for (int i = last_i; i < s.size(); ++i) {
            if (s[i] == p[0]) ++cnt;
            else if (s[i] == p[1]) --cnt;
            if (cnt >= 0) continue;
            for (int j = last_j; j <= i; ++j) {
                if (s[j] == p[1] && (j == last_j || s[j] != s[j - 1])) {
                    helper(s.substr(0, j) + s.substr(j + 1), i, j, p, res);
                }
            }
            return;
        }
        string rev = string(s.rbegin(), s.rend());
        if (p[0] == '(') helper(rev, 0, 0, {')', '('}, res);
        else res.push_back(rev);
    }
};

 

相似题目:

Different Ways to Add Parentheses

Longest Valid Parentheses

Generate Parentheses

Valid Parentheses

 

参考资料:

https://leetcode.com/problems/remove-invalid-parentheses/

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75032/share-my-java-bfs-solution

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75027/easy-short-concise-and-fast-java-dfs-3-ms-solution

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75046/c-depth-limited-dfs-3ms-eliminate-duplicates-without-hashmap

 

LeetCode All in One 题目讲解汇总(持续更新中...)

相关文章
相关标签/搜索