題一
Implement regular expression matching with support for ‘.’ and ‘*’.
‘.’ Matches any single character.
‘*’ Matches zero or more of the preceding element.The matching should cover the entire input string (not partial).
The function prototype should be:
boolean isMatch(String s, String p)Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → true
不考慮轉義等特殊情況的匹配。
正則的常見實現方式有三種:DFA、Backtracking、NFA。
這裏簡單的匹配,可以用以下兩種方式,第一種通過遞歸的方式,第二種通過動態規劃。
遞歸
首先看遞歸,這裏主要共考慮三種情況。
1. 首字符爲’*’,直接判定不合法。例如”*sdf”,”s**df”。
2. 第二個字符不爲’*’,此時需要判斷第一個字符是否可以匹配匹配串的第一個字符,相等或者’.’。相等則匹配串和模式串都截去首字符,遞歸處理。
3. 第二個字符爲’*’,當模式串和匹配串的首字符匹配,此時需要進行兩種處理,第一種認爲模式串首字符出現0次,跳過模式串前兩個字符,進行再匹配。第二種認爲模式串首字符出現了多次,再將匹配串截取,再匹配,直至匹配成功或者失敗。當模式串和匹配串首字符不匹配,跳過模式串前兩個字符,進行再匹配。
遞歸解決代碼如下:
boolean isMatch(String s, String p) {
if (p.isEmpty())
return s.isEmpty();
if (p.startsWith("*"))
return false;
if (p.length() == 1 || p.charAt(1) != '*') {
if (s.isEmpty() || (p.charAt(0) != '.' && p.charAt(0) != s.charAt(0))) {
return false;
} else {
return isMatch(s.substring(1), p.substring(1));
}
}
while (!s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.')) {
if (isMatch(s, p.substring(2))) {//出現0次
return true;
}
s = s.substring(1);
}
//出現0次
return isMatch(s, p.substring(2));
}
動態規劃
動態規劃,一般解題思路:
- 刻畫一個最優解的結構特徵。
- 遞歸的定義最優解的的值。
- 計算最優解的值,通常採用自底向上方法。
- 利用計算出的信息構造一個最優解。
按照以上步驟來解決這道題。
尋找最優子結構:
找到最優子結構,然後利用這個子結構從子問題的最優解構造出原問題的最優解。
設匹配串
S=s_1,s_2,...,s_m ,模式串P=p_1,p_2,...,p_n ,最終是否匹配成功記爲isMatch,可看爲布爾型。
1. 如果p_n≠ ’*’,若s_m 和p_n 匹配,則isMatch爲S_m−1 和P_n−1 匹配的結果。
2. 如果p_n =’*’,若p_n−1 和s_m 匹配,則isMatch爲S_m 和P_n−1 ,S_m 和P_n−2 ,S_m−1 和P_n 匹配結果進行或處理後的值。若不匹配,則isMatch爲S_m 和P_n−2 匹配的結果。
遞歸解:
用一個布爾類型的二維數組來建立最優解的遞歸式,通過上述,可以寫成如下:
計算:
通過自底向上地計算。匹配問題共有m*n個子問題。
結果:
直接獲取二維數組最後的值,即爲匹配的結果。
部分過程模擬示意圖如下:
動態規劃解決代碼如下:
boolean isMatchByDP(String s, String p) {
if (p.isEmpty()) {
return s.isEmpty();
}
int len_s = s.length(), len_p = p.length();
//申請,默認爲false
boolean[][] dp = new boolean[len_s + 1][len_p + 1];
dp[0][0] = true;
//處理特殊情況,s爲空的情況,例如 ""和".*"
for (int i = 0; i < len_p; i++) {
if (p.charAt(i) == '*' && dp[0][i - 1]) {
dp[0][i + 1] = true;
}
}
for (int i = 0; i < len_s; i++) {
for (int j = 0; j < len_p; j++) {
if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.')
dp[i + 1][j + 1] = dp[i][j];
else if (p.charAt(j) == '*') {
if (s.charAt(i) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
//出現一次,0次,出現多次
dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i + 1][j - 1] || dp[i][j + 1]);
} else {
//出現0次
dp[i + 1][j + 1] = dp[i + 1][j - 1];
}
}
}
}
return dp[len_s][len_p];
}
題二
阿里的筆試題,由題一簡單變換下。’?’匹配每個字符,’*’匹配任意串。簡單修改部分代碼即可。、
遞歸
遞歸解決如下:
boolean isMatch(String s, String p) {
if (p.isEmpty()) {
return s.isEmpty();
}
if (p.charAt(0) != '*') {
if (s.isEmpty() || (p.charAt(0) != s.charAt(0) && p.charAt(0) != '?')) {
return false;
} else {
return isMatch(s.substring(1), p.substring(1));
}
}
while (!s.isEmpty() && (p.charAt(0) == '*')) {
if (isMatch(s, p.substring(1))) {
return true;
}
s = s.substring(1);
}
return isMatch(s, p.substring(1));
}
動態規劃
動態規劃解決如下:
boolean isMatch_dp(String p, String p) {
if (p.isEmpty()) {
return p.isEmpty();
}
boolean[][] dp = new boolean[p.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int i = 0; i < p.length(); i++) {
if (p.charAt(i) == '*' && dp[0][i])
dp[0][i + 1] = true;
}
for (int i = 0; i < p.length(); i++) {
for (int j = 0; j < p.length(); j++) {
if (p.charAt(i) == p.charAt(j) || p.charAt(j) == '?') {
dp[i + 1][j + 1] = dp[i][j];
} else {
if (p.charAt(j) == '*') {
//多次,一次
dp[i + 1][j + 1] = dp[i][j + 1] || dp[i][j];
}
}
}
}
return dp[p.length()][p.length()];
}