動態(tài)遞歸之正則表達(dá)式實(shí)戰(zhàn)案例(含Java代碼)
題目:正則表達(dá)式
給你一個字符串 s 和一個字符規(guī)律 p,請你來實(shí)現(xiàn)一個支持 ‘.’ 和 ‘*’ 的正則表達(dá)式匹配。
‘.’ 匹配任意單個字符
‘*’ 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s 的,而不是部分字符串。
示例 1:
輸入:s = “aa”, p = “a”
輸出:false
解釋:“a” 無法匹配 “aa” 整個字符串。
示例 2:
輸入:s = “aa”, p = “a*”
輸出:true
解釋:因?yàn)?‘*’ 代表可以匹配零個或多個前面的那一個元素, 在這里前面的元素就是 ‘a’。因此,字符串 “aa” 可被視為 ‘a’ 重復(fù)了一次。
示例 3:
輸入:s = “ab”, p = “."
輸出:true
解釋:".” 表示可匹配零個或多個(‘*’)任意字符(‘.’)。
提示:
1 <= s.length <= 20
1 <= p.length <= 20
s 只包含從 a-z 的小寫字母。
p 只包含從 a-z 的小寫字母,以及字符 . 和 *。
保證每次出現(xiàn)字符 * 時,前面都匹配到有效的字符
題解
方法一:帶記憶化遞歸
思路:
遞歸函數(shù) match(i, j) 表示字符串 s 從 i 開始到末尾的子串和模式 p 從 j 開始到末尾的子串是否匹配。
考慮以下情況:
- 如果 j 已經(jīng)到達(dá) p 的末尾,那么只有當(dāng) i 也到達(dá) s 的末尾才匹配成功。
- 首先檢查當(dāng)前第一個字符是否匹配:first_match = (i < s.length()) 且 (s.charAt(i) == p.charAt(j) 或 p.charAt(j) == ‘.’)
- 如果下一個字符是 ‘*’ (即 j+1 < p.length() 且 p.charAt(j+1)==‘*’),那么有兩種情況:
a. 匹配0個前面的字符:則跳過模式中的"x*"(即j+2),繼續(xù)匹配 match(i, j+2)
b. 匹配1個或多個前面的字符:在第一個字符匹配的前提下,匹配s的下一個字符,模式保持不變(因?yàn)?可以匹配多個),即 match(i+1, j) - 否則,如果沒有’*',那么當(dāng)前字符必須匹配,然后遞歸匹配剩下的部分:match(i+1, j+1)
但是注意:遞歸可能會出現(xiàn)重復(fù)子問題,所以效率可能不高,但作為解決方案之一。
public class RegularExpMatch {
// 使用Map存儲已計算的結(jié)果,避免重復(fù)計算
private Map<String, Boolean> memo = new HashMap<>();
public boolean isMatch(String s, String p) {
return dp(0, 0, s, p);
}
private boolean dp(int i, int j, String s, String p) {
// 生成唯一鍵值對,用于記憶化存儲
String key = i + "," + j;
if (memo.containsKey(key)) {
return memo.get(key);
}
// 模式串已用完
if (j == p.length()) {
return i == s.length();
}
// 檢查當(dāng)前字符是否匹配
boolean firstMatch = (i < s.length()) &&
(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
boolean result;
// 處理'*'的情況(需要確保j+1不越界)
if (j + 1 < p.length() && p.charAt(j + 1) == '*') {
// 兩種情況:
//1. 匹配0個字符(跳過當(dāng)前字符和*)即匹配0次,不消耗任何字符串字符
//2. 匹配1個或多個字符(繼續(xù)匹配)
result = dp(i, j + 2, s, p) || (firstMatch && dp(i + 1, j, s, p));
} else {
// 沒有'*',正常匹配下一個字符
result = firstMatch && dp(i + 1, j + 1, s, p);
}
// 存儲計算結(jié)果
memo.put(key, result);
return result;
}
}
方法二:動態(tài)規(guī)劃
- 初始化:空字符串匹配空模式
- 處理模式開頭可能的"x*"匹配空字符串的情況
- 狀態(tài)轉(zhuǎn)移分三種情況:
- 普通字符匹配
- '.'匹配任意字符
- '*'的兩種處理方式(匹配0次或多次)
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
// dp[i][j]表示s的前i個字符和p的前j個字符是否匹配
boolean[][] dp = new boolean[m + 1][n + 1];
// 空字符串匹配空模式
dp[0][0] = true;
// 處理模式開頭可能有 "a*" 或 ".*" 的情況(匹配空字符串)
for (int j = 2; j <= n; j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2]; // 跳過 "x*" 模式
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
char sc = s.charAt(i - 1);
char pc = p.charAt(j - 1);
// 當(dāng)前字符匹配
if (sc == pc || pc == '.') {
//如果當(dāng)前字符匹配,此時的值為去掉當(dāng)前字符后是否匹配的值
dp[i][j] = dp[i - 1][j - 1];
}
// 處理 '*' 的情況
else if (pc == '*') {
char prev = p.charAt(j - 2); // '*' 前面的字符,注意i和j表示的是dp表的下標(biāo),不是字符串下標(biāo),字符值下標(biāo)還需要-1
// 1. 匹配0個字符(跳過 "x*" 模式)
dp[i][j] = dp[i][j - 2];
// 2. 匹配1個或多個字符(如果前一個字符匹配)
if (prev == '.' || prev == sc) {
dp[i][j] = dp[i][j] || dp[i - 1][j]; //表示如果匹配0個字符成立就直接返回true了,否則再匹配1個或多個字符
}
}
}
}
return dp[m][n];
}
考慮字符串 s = “abcde” 和模式 p = “abcc*d*.*de”,我們使用動態(tài)規(guī)劃來解決匹配問題。動態(tài)規(guī)劃表如下:
dp[i][j] 表示 s 的前 i 個字符與 p 的前 j 個字符是否匹配。
| s\p | 0 | 1:a | 2:b | 3:c | 4:c | 5:* | 6:d | 7:* | 8:. | 9:* | 10:d | 11:e |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | T | F | F | F | F | F | F | F | F | F | F | F |
| 1:a | F | T | F | F | F | F | F | F | F | F | F | F |
| 2:b | F | F | T | F | F | F | F | F | F | F | F | F |
| 3:c | F | F | F | T | F | T | F | T | F | T | F | F |
| 4:d | F | F | F | F | F | F | T | T | F | T | T | F |
| 5:e | F | F | F | F | F | F | F | F | F | F | F | T |
總結(jié):
匹配0真的難繃,匹配0個我以為只是’*‘沒了但其前面的元素還在,結(jié)果是’*'和前面一個元素都沒了?。。?!
到此這篇關(guān)于動態(tài)遞歸之正則表達(dá)式的文章就介紹到這了,更多相關(guān)動態(tài)遞歸之正則表達(dá)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成Nacos實(shí)現(xiàn)注冊中心與配置中心流程詳解
這篇文章主要介紹了SpringBoot集成Nacos實(shí)現(xiàn)注冊中心與配置中心流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02
Java中將String類型轉(zhuǎn)換為int類型的五種方法及常見問題分析
在Java中將String類型轉(zhuǎn)換為int類型是一個常見的操作,因?yàn)樵趯?shí)際開發(fā)中,我們經(jīng)常需要從用戶輸入或者外部數(shù)據(jù)源中獲取字符串形式的數(shù)字,并將其轉(zhuǎn)換為整數(shù)進(jìn)行計算和處理,在Java中,有幾種方法可以實(shí)現(xiàn)這種轉(zhuǎn)換,下面我將逐一介紹這些方法,需要的朋友可以參考下2025-06-06
Java自動化測試中多數(shù)據(jù)源的切換(實(shí)例講解)
下面小編就為大家?guī)硪黄狫ava自動化測試中多數(shù)據(jù)源的切換(實(shí)例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
Spring 框架需要的 jar 包解析與使用小結(jié)
本文總結(jié)Spring4.x項(xiàng)目的核心jar包作用及最小依賴組合,涵蓋IoC容器、AOP、Web、數(shù)據(jù)訪問等模塊,建議使用Maven/Gradle管理依賴,避免手動配置導(dǎo)致版本混亂,助力開發(fā)者高效維護(hù)Spring應(yīng)用,感興趣的朋友跟隨小編一起看看吧2025-09-09
Spring中自帶的@Schedule實(shí)現(xiàn)自動任務(wù)的過程解析
這篇文章主要介紹了關(guān)于Spring中自帶的@Schedule實(shí)現(xiàn)自動任務(wù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06

