LeetCode-678. 有效的括號字串
阿新 • • 發佈:2022-04-01
題目來源
題目詳情
給定一個只包含三種字元的字串:(
,)
和 *
,寫一個函式來檢驗這個字串是否為有效字串。有效字串具有如下規則:
- 任何左括號
(
必須有相應的右括號)
。 - 任何右括號
)
必須有相應的左括號(
。 - 左括號
(
必須在對應的右括號之前)
。 -
*
可以被視為單個右括號)
,或單個左括號(
,或一個空字串。 - 一個空字串也被視為有效字串。
示例 1:
輸入: "()"
輸出: True
示例 2:
輸入: "(*)"
輸出: True
示例 3:
輸入: "(*))"
輸出: True
注意:
- 字串大小將在 [1,100] 範圍內。
相似題目
題號 | 題目 | 備註 |
---|---|---|
20 | 有效的括號 | 棧 |
22 | 括號生成 | dfs |
5 | 最長迴文子串 | dp |
647 | 迴文子串 | dp |
32 | 最長有效括號 | dp |
678 | 有效的括號字串 | dp |
題解分析
解法一:二維動態規劃
- 本題一看題目就知道是一道動態規劃相關的題目,而且這個題型與之前遇到的5-最長迴文子串以及647-迴文子串這兩道題目很相似,因為它們都是一種需要考慮中間子串的情況。這是與32-最長有效括號這道題目最大的一個區別,因為LeetCode-32這道題只有左右括號兩種字元,所以它可以使用一維的dp陣列來表示最長有效括號的長度,但是本題除了左右括號還有一個特殊的'*'號,它可以充當任意一個符號,甚至空字元,這就導致瞭如果使用一維dp將無法根據前面的狀態進行轉移。
- 因此,這裡我們藉助迴文子串的解題思路,定義一個
dp
陣列為boolean[][] dp
,其中\(dp[i][j]\)表示(i,j)的子串是否是有效的括號字串。而它們的狀態轉移如下:
- 上面的狀態轉移方程涉及到兩種情況:
- 第一種,如果i和j所處的字元是合法的括號對,即分別為
(
和)
字元,或者是使用了‘*’替換後的有效括號對,那麼\(dp[i][j]\)的狀態可以根據\(dp[i+1][j-1]\)來判斷。 - 第二種,如果上述得出的\(dp[i][j]\)是false,那就要進入這種情況的判斷了。我們需要遍歷i到j之間的字元,看是否可以找到一個字元k,使得(i,k)和(k,j)都是有效的括號字串,如果能找到說明\(dp[i][j]\)
- 第一種,如果i和j所處的字元是合法的括號對,即分別為
- 本題需要注意的是,因為我們都是成對來考慮的,所以我們需要對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];
}
}