1. 程式人生 > 其它 >LeetCode-678. 有效的括號字串

LeetCode-678. 有效的括號字串

題目來源

678. 有效的括號字串

題目詳情

給定一個只包含三種字元的字串:(  和 *,寫一個函式來檢驗這個字串是否為有效字串。有效字串具有如下規則:

  1. 任何左括號 ( 必須有相應的右括號 )
  2. 任何右括號 ) 必須有相應的左括號 ( 。
  3. 左括號 ( 必須在對應的右括號之前 )
  4. * 可以被視為單個右括號 ) ,或單個左括號 ( ,或一個空字串。
  5. 一個空字串也被視為有效字串。

示例 1:

輸入: "()"
輸出: True

示例 2:

輸入: "(*)"
輸出: True

示例 3:

輸入: "(*))"
輸出: True

注意:

  1. 字串大小將在 [1,100] 範圍內。

相似題目

題號 題目 備註
20 有效的括號
22 括號生成 dfs
5 最長迴文子串 dp
647 迴文子串 dp
32 最長有效括號 dp
678 有效的括號字串 dp

題解分析

解法一:二維動態規劃

  1. 本題一看題目就知道是一道動態規劃相關的題目,而且這個題型與之前遇到的5-最長迴文子串以及647-迴文子串這兩道題目很相似,因為它們都是一種需要考慮中間子串的情況。這是與32-最長有效括號這道題目最大的一個區別,因為LeetCode-32這道題只有左右括號兩種字元,所以它可以使用一維的dp陣列來表示最長有效括號的長度,但是本題除了左右括號還有一個特殊的'*'號,它可以充當任意一個符號,甚至空字元,這就導致瞭如果使用一維dp將無法根據前面的狀態進行轉移。
  2. 因此,這裡我們藉助迴文子串的解題思路,定義一個dp陣列為boolean[][] dp,其中\(dp[i][j]\)表示(i,j)的子串是否是有效的括號字串。而它們的狀態轉移如下:
\[dp[i][j] = dp[i+1][j-1] || (dp[i][k] \&\& dp[k+1][j]) \]
  1. 上面的狀態轉移方程涉及到兩種情況:
    • 第一種,如果i和j所處的字元是合法的括號對,即分別為()字元,或者是使用了‘*’替換後的有效括號對,那麼\(dp[i][j]\)的狀態可以根據\(dp[i+1][j-1]\)來判斷。
    • 第二種,如果上述得出的\(dp[i][j]\)是false,那就要進入這種情況的判斷了。我們需要遍歷i到j之間的字元,看是否可以找到一個字元k,使得(i,k)和(k,j)都是有效的括號字串,如果能找到說明\(dp[i][j]\)
      也是合法的括號字串。
  2. 本題需要注意的是,因為我們都是成對來考慮的,所以我們需要對dp陣列進行一些特殊的初始化,比如需要分別初始化字串長度為1和2時候的dp狀態,這些狀態的判斷比較簡單,這裡就不詳細說明了。
class Solution {
    public boolean checkValidString(String s) {
        int n = s.length();
        // dp[i][j]表示以(i,j)的子串是否是有效的括號字串
        // dp[i][j] = dp[i+1][j-1] || (dp[i][k] && dp[k+1][j])
        boolean[][] dp = new boolean[n][n];
        for(int i=0; i<n; i++){
            if(s.charAt(i) == '*'){// '*'可以被視為一個空字串,所以是合法的
                dp[i][i] = true;
            }
        }

        for(int i=1; i<n; i++){
            char ch1 = s.charAt(i-1);
            char ch2 = s.charAt(i);
            if((ch1 == '(' || ch1 == '*') && (ch2 == ')' || ch2 == '*')){
                // 長度為2的子串,且滿足匹配規則則是合法的
                dp[i-1][i] = true;
            }
        }

        for(int i=n-3; i>=0; i--){
            char ch1 = s.charAt(i);
            for(int j=i + 2; j<n; j++){
                char ch2 = s.charAt(j);

                if((ch1 == '(' || ch1 == '*') && (ch2 == ')' || ch2 == '*')){
                    // 子串的格式為:(*****)
                    dp[i][j] = dp[i+1][j-1];
                }

                if(!dp[i][j]){
                    for(int k=i; k<j; k++){
                        // 子串格式為:()*****()
                        dp[i][j] = dp[i][k] && dp[k+1][j];
                        if(dp[i][j]){
                            break;
                        }
                    }
                }
            }
        }

        return dp[0][n-1];
    }
}