LeetCode 不只是題解(10.正規表示式匹配[困難])
題目
給你一個字串 s 和一個字元規律 p,請你來實現一個支援 ‘.’ 和 ‘*’ 的正規表示式匹配。
‘.’ 匹配任意單個字元
‘*’ 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字串 s的,而不是部分字串。
說明:
s 可能為空,且只包含從 a-z 的小寫字母。
p 可能為空,且只包含從 a-z 的小寫字母,以及字元 . 和 *。
示例 1:
輸入:
s = “aa”
p = “a”
輸出: false
解釋: “a” 無法匹配 “aa” 整個字串。
示例 2:
輸入:
s = “aa”
p = “a*”
輸出: true
解釋: 因為 ’ * ’ 代表可以匹配零個或多個前面的那一個元素, 在這裡前面的元素就是 ‘a’。因此,字串 “aa” 可被視為 ‘a’ 重複了一次。
示例 3:
輸入:
s = “ab”
p = ". * "
輸出: true
解釋: ". * " 表示可匹配零個或多個(’*’)任意字元(’.’)。
示例 4:
輸入:
s = “aab”
p = “cab”
輸出: true
解釋: 因為 ‘*’ 表示零個或多個,這裡 ‘c’ 為 0 個, ‘a’ 被重複一次。因此可以匹配字串 “aab”。
示例 5:
輸入:
s = “mississippi”
p = “misisp*.”
輸出: false
題目解析
p : a * b c *
為了描述方便,我們將上述的a * 、b、c *分別成為一次匹配的要求。
從題目中可以提取中一些關鍵的地方:
-
匹配的是整個字串。兩個字串要相互匹配才可以,任何一個有多餘都不可以;
-
注意. *是可以匹配任意字元任意次。一個. *可以匹配任意的字串;
-
注意*對匹配的影響:
s : aaaaabc q: a*aaabc
很明顯這個例子應該是true。從上面例子應該能發現一些不對勁的地方,就是*的匹配次數不僅取決於本次匹配的要求,而且還取決於下次匹配的要求。
題目解析及優化
-
遞迴法
我們首先要求和把每次提取要求的函式定義到外部方便使用,其他地方都好說,關鍵是出現字元+ * 的形式部分的寫法。
我們的思路也很簡單。* 這小子不是匹配的次數不一定嘛,好說,我們每次都試試,如果有一次是正確的,本次就是可以匹配的。出現*,就往後遞迴,把可能匹配的情況都寫出來去尋找。
具體的實現思路:
其實這部分無非兩種情況:
s : abcd p : af*bcd
s : abbbc p : ab*c
- 當出現 * 的匹配要求的字元和s部分的不匹配(b和f *):此時直接忽略f * 直接向後繼續匹配即可
- 當出現 * 的匹配要求的字元和s部分的匹配(bbb和b *): 此時只需要兩個遞迴 :一個忽略b *向後繼續匹配;另一個將一個b和b * 匹配,繼續向後匹配。
貼上程式碼:
//匹配的要求 struct request { char c; int leixing; }; //型別為0為*,1為匹配一次 request find_one_requ(string q, int index) { request ans; ans.c = q[index]; if (index < q.size() - 1 && q[index + 1] == '*') { ans.leixing = 0; } else { ans.leixing = 1; } return ans; } bool isMatch(string s, string p) { int i = 0, j = 0; //以p為主進行迭代 for (; i < p.size(); i++) { request re = find_one_requ(p, i); //如果s匹配完時退出 if (re.leixing && j == s.size()) { return false; } //如果只需匹配一次時 if (re.leixing) { if (re.c == '.' || re.c == s[j]) { j++; } else if (re.c != s[j]) { return false; } //出現*的時候 } else { //需要出現兩次迭代的情況 if ((re.c == '.' || re.c == s[j]) && j < s.size()) { return isMatch(s.substr(j, s.size() - j), p.substr(i + 2, p.size() - i - 2)) || isMatch(s.substr(j + 1, s.size() - j - 1), p.substr(i, p.size() - i)); //只需迭代一次的情況 } else { return isMatch(s.substr(j, s.size() - j), p.substr(i + 2, p.size() - i - 2)); } i++; } } //當p迭代完時根據情況返回 if (j == s.size()) { return true; } else { return false; } }
-
動態規劃
再分析一波上面解法的思路
s : bbbc p : b * b * c
看下圖即可:
一旦有兩個以上的*號就很有可能會出現重複的計算。而且大量使用遞迴會導致佔用更大的記憶體。避免重複計算的方法正是動態規劃。
每一次匹配都是基於之前的匹配結果的,所以可以自然的想到動態規劃的開始是兩個空串可以相互匹配,所以搭建dp [ i ] [ j ]的二維陣列而且dp [ 0 ] [ 0 ]為true;
狀態轉移方程我們可以參考遞迴方法的遞迴部分,思路一致,只是寫法有所區別:
- 首先並不需要匹配要求的結構體而直接一個字元一個字元往後匹配即可。這是因為當dp[ i ] [ j ]匹配到 * 的時候,* 前面的字元已經進行了一次匹配,我們視作dp[ i ] [ j - 1 ]為匹配一次的情況即可。並不影響我們的判斷。
- 分情況依舊按照匹配要求的型別去分,只是這裡只需要判斷匹配的是不是 * 即可判斷匹配的型別。
這裡就直接貼官方的程式碼:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
auto matches = [&](int i, int j) {
if (i == 0) {
return false;
}
if (p[j - 1] == '.') {
return true;
}
return s[i - 1] == p[j - 1];
};
vector <vector<int>> f(m + 1, vector<int>(n + 1));
f[0][0] = true;
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
f[i][j] |= f[i][j - 2];
if (matches(i, j - 1)) {
f[i][j] |= f[i - 1][j];
}
} else {
if (matches(i, j)) {
f[i][j] |= f[i - 1][j - 1];
}
}
}
}
return f[m][n];
}
};
總結及心得
-
本題的破題點就是當不知道 * 匹配幾次的時候全部匹配一次,有一次成功即可,這就形成了遞迴法的思路;動態規劃則需要對整個過程比較熟悉才能寫出,我個人的建議是先思考遞迴,再用動態規劃去模擬遞迴的思路去降低時間和空間複雜度;
-
在寫遞迴式的時候一定要考慮到邊界問題,有時遞迴的隱含條件和邊界有關,如果忽略可能會導致陣列越界;
-
一種特殊函式的寫法
auto matches = [&](int i, int j) { if (i == 0) { return false; } if (p[j - 1] == '.') { return true; } return s[i - 1] == p[j - 1]; };
這樣可以直接在函式內部定義,可以避免傳引數的問題,也可以避免不想傳參就定義全域性變數的問題;
相關文章
- Leetcode 10. 正規表示式匹配LeetCode
- leetcode - 正規表示式匹配LeetCode
- leetcode題目10之正規表示式匹配LeetCode
- 正規表示式匹配原理
- [譯]正規表示式匹配
- 字串——正規表示式匹配字串
- iOS正規表示式匹配iOS
- [LeetCode] Regular Expression Matching 正規表示式匹配LeetCodeExpress
- 正規表示式支配匹配模式模式
- JavaScript匹配中文正規表示式JavaScript
- 模式匹配與正規表示式模式
- Swift 正規表示式匹配NSRegularExpressionSwiftExpress
- python 正規表示式匹配Python
- 正規表示式教程之位置匹配詳解
- LeetCode題解(Offer19):正規表示式匹配的*和.實現(Python)LeetCodePython
- SITA報文解碼匹配的正規表示式
- 匹配html標籤正規表示式HTML
- 匹配空白字元正規表示式字元
- 匹配 XML 檔案正規表示式XML
- 匹配 HTML 標籤正規表示式HTML
- 匹配空行正規表示式程式碼
- 常用正規表示式匹配模式(java)模式Java
- JavaScript匹配註釋正規表示式JavaScript
- [JavaScript] 正規表示式單次匹配與多次匹配JavaScript
- 正規表示式教程之匹配單個字元詳解字元
- 正規表示式教程之匹配一組字元詳解字元
- 匹配iphone手機序列正規表示式iPhone
- 匹配HTML註釋的正規表示式HTML
- 匹配A股程式碼的正規表示式
- 正規表示式匹配標點符號符號
- 匹配HTML標籤的正規表示式HTML
- 正規表示式匹配雙位元組字元字元
- js正規表示式如何匹配註釋JS
- 匹配javascript註釋的正規表示式JavaScript
- Oracle正規表示式匹配中文的方法Oracle
- 匹配正負小數正規表示式程式碼
- LeetCode-10. 正規表示式匹配(Python-re包)LeetCodePython
- 例項程式碼詳解正規表示式匹配換行