[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 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索