劍指 Offer 19. 正規表示式匹配
難度 困難
請實現一個函式用來匹配包含’. ‘和’*‘的正規表示式。模式中的字元’.’表示任意一個字元,而’*‘表示它前面的字元可以出現任意次(含0次)。在本題中,匹配是指字串的所有字元匹配整個模式。例如,字串”aaa”與模式”a.a”和”abaca”匹配,但與”aa.a”和”ab*a”均不匹配。
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 = “c*a*b”
輸出: true
解釋: 因為 ‘*’ 表示零個或多個,這裡 ‘c’ 為 0 個, ‘a’ 被重複一次。因此可以匹配字串 “aab”。
示例 5:
輸入:
s = “mississippi”
p = “mis*is*p*.”
輸出: false
一開始的思路,,,,好吧沒有思路 只能使用字串中關於正則的匹配API,程式碼如下
class Solution {
public boolean isMatch(String s, String p) {
return s.matches(p);
}
}
其實我們仔細考慮一下,無非就是分三種情況
假設主串為 A,模式串為 B , A 的長度為 n ,B 的長度為 m ,關注正規表示式 B 的最後一個字元是誰,它有三種可能,正常字元,* 和 .(點),那針對這三種情況討論即可,如下:
如果 B 的最後一個字元是正常字元,那就是看 A[n-1] 是否等於 B[m-1],相等則看 A_{0..n-2}與 B_{0..m-2},不等則是不能匹配,這就是子問題。
如果 B 的最後一個字元是 .(點) ,它能匹配任意字元,直接看 A_{0..n-2}與 B_{0..m-2}
如果 B 的最後一個字元是 * ,它代表 B[m-2]=c 可以重複0次或多次,它們是一個整體 c*
- 情況一:A[n-1]是 0個 c,B 最後兩個字元廢了,能否匹配取決A_{0..n-1}和 B_{0..m-3}是否匹配
- 情況二:A[n-1]是多個 c 中的最後一個(這種情況必須 A[n−1]=c 或者 c=’.’),所以 A 匹配完往前挪一個,B 繼續匹配,因為可以匹配多個,繼續看 A_{0..n-2}和 B_{0..m-1}是否匹配。
先來個某大神動態規劃的思路
轉移方程
f[i][j] 代表 A 的前 i 個和 B 的前 j 個能否匹配
對於前面兩個情況,可以合併成一種情況 f[i][j] = f[i-1][j-1]
對於第三種情況,對於 c* 分為看和不看兩種情況
- 不看:直接砍掉正則串的後面兩個, f[i][j] = f[i][j-2]
- 看:正則串不動,主串前移一個,f[i][j] = f[i-1][j]
特判:需要考慮空串空正則
- 空串和空正則是匹配的,f[0][0] = true
- 空串和非空正則,不能直接定義 true 和 false ,必須要計算出來。(比如A=” “ ,B=abc*)
- 非空串和空正則必不匹配,f[1][0]=…=f[n][0]=false
- 非空串和非空正則,那肯定是需要計算的了。
大體上可以分為空正則和非空正則兩種,空正則也是比較好處理的,對非空正則我們肯定需要計算,非空正則的三種情況,前面兩種可以合併到一起討論,第三種情況是單獨一種,那麼也就是分為當前位置是 * 和不是 * 兩種情況了。
再來個遞迴程式碼,遞迴程式碼與上面的思路其實是一樣的
package ddx.september.day29;
public class Normal_19 {
public boolean isMatch(String s, String p) {
if (s.length() == 0 ) {
//主串為空
if (p.length() % 2 != 0) {
//模式串長度為奇數,則一定不匹配,因為抵消不了奇數
//例如:"." , "ab*","abc"
return false;
} else {
//長度為偶數,只需要判斷偶數位上是不是*即可!
int i = 1;
while (i < p.length()) {
if (p.charAt(i) != '*') {
return false;
}
i += 2;
}
return true;
}
} else if (p.length() == 0) {
//模式串為空
return false;
} else {
char ch1 = s.charAt(0); //s的首位
char ch2 = p.charAt(0); //p的首位
char ch3 = 'a'; //p的下一位
if(p.length() > 1){
ch3 = p.charAt(1);
}
//照樣分情況,看看是不是*
if(ch3 != '*'){
if(ch2 == ch1 || ch2 == '.'){
return isMatch(s.substring(1),p.substring(1));
}else{
//連正常字元和.都匹配不上
return false;
}
}else{
//難點就在於ch3是 *
//正常字元相等,或者是.
//即選擇或者不選擇!!!
//"aa"
//"a*a"
//上面兩個字串,碰到*可以選擇,也可以不選擇
if(ch2 == ch1 || ch2 == '.'){
//前者是選擇,後者是不選擇
return isMatch(s.substring(1),p) || isMatch(s,p.substring(2));
}else{
//連正常字元和.都匹配不上,那麼ch2 和 ch3廢了!!!!
return isMatch(s,p.substring(2));
}
}
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結