力扣- -正则表达式匹配
力扣- -正則表達式匹配
文章目錄
- 力扣- -正則表達式匹配
- 一、題目描述
- 二、分析
- 方法一:Dp函數
- 明確狀態和選擇
- 確定狀態轉譯方程
- 確定base case
- 完整代碼
- 方法二:Dp table
- 明確狀態和選擇
- 明確狀態轉移方程
- 確定base case
- 完整代碼
一、題目描述
二、分析
-
這兩個通配符是最常用的,其中點號「.」可以匹配任意一個字符,星號「*」可以讓之前的那個字符重復任意次數(包括 0 次)。
-
比如說模式串".a * b"就可以匹配文本"zaaab",也可以匹配"cb";模式串"a..b"可以匹配文本"amnb";而模式串".*"就比較牛逼了,它可以匹配任何文本。
-
題目會給我們輸入兩個字符串s和p,s代表文本,p代表模式串,請你判斷模式串p是否可以匹配文本s。
-
我們可以假設模式串只包含小寫字母和上述兩種通配符且一定合法,不會出現*a或者b**這種不合法的模式串,
-
函數簽名如下:
-
對于我們將要實現的這個正則表達式,難點在那里呢?
-
點號通配符其實很好實現,s中的任何字符,只要遇到.通配符,無腦匹配就完事了。
-
主要是這個星號通配符不好實現,一旦遇到*通配符,前面的那個字符可以選擇重復一次,可以重復多次,也可以一次都不出現,這該怎么辦?
-
對于這個問題,答案很簡單,對于所有可能出現的情況,全部窮舉一遍,只要有一種情況可以完成匹配,就認為p可以匹配s。那么一旦涉及兩個字符串的窮舉,我們就應該條件反射地想到動態規劃的技巧了。
方法一:Dp函數
- 整個s和p相互匹配的過程大致是,兩個指針i, j分別在s和p上移動,如果最后兩個指針都能移動到字符串的末尾,那么久匹配成功,反之則匹配失敗。
- 正則表達算法問題只需要把住一個基本點:看兩個字符是否匹配,一切邏輯圍繞匹配/不匹配兩種情況展開即可
- 如果不考慮*通配符,面對兩個待匹配字符s[i]和p[j],我們唯一能做的就是看他倆是否匹配:
那么考慮一下,如果加入*通配符,局面就會稍微復雜一些,不過只要分情況來分析,也不難理解。
因為【 * 】號影響的范圍是根據【 * 】號前面的情況來進行判斷匹配0次還是多次的,所以當p[j + 1]為*通配符時,我們分情況討論下:
- 如果匹配,即s[i] == p[j],那么有兩種情況:
- p[j]有可能會匹配多個字符,比如s = "aaa", p = "a*",那么p[0]會通過*匹配 3 個字符"a"。
- p[i]也有可能匹配 0 個字符,比如s = "aa", p = "a*aa",由于后面的字符可以匹配s,所以p[0]只能匹配 0 次。
- 如果不匹配,即s[i] != p[j],只有一種情況:
- p[j]只能匹配 0 次,然后看下一個字符是否能和s[i]匹配。比如說s = "aa", p = "b*aa",此時p[0]只能匹配 0 次。
綜上,可以把之前的代碼針對*通配符進行一下改造:
bool isMatch(string s, string p) {int i = 0, j = 0;while (i < s.size() && j < p.size()) {if (s[i] == p[j] || p[j] == '.') {// 匹配if (j < p.size() - 1 && p[j + 1] == '*') {// 有 * 通配符,可以匹配 0 次或多次} else {// 無 * 通配符,老老實實匹配 1 次i++; j++;}} else {// 不匹配if (j < p.size() - 1 && p[j + 1] == '*') {// 有 * 通配符,只能匹配 0 次} else {// 無 * 通配符,匹配無法進行下去了return false;}}}return i == j; }- 整體的思路已經很清晰了,但現在的問題是,遇到*通配符時,到底應該匹配 0 次還是匹配多次?多次是幾次?
你看,這就是一個做「選擇」的問題,要把所有可能的選擇都窮舉一遍才能得出結果。
明確狀態和選擇
-
動態規劃算法的核心就是「狀態」和「選擇」,「狀態」無非就是i和j兩個指針的位置,「選擇」就是p[j]選擇匹配幾個字符。
-
根據「狀態」,我們可以定義一個dp函數:
-
dp函數的定義如下:
-
若dp(s,i,p,j) = true,則表示s[i..]可以匹配p[j..];若dp(s,i,p,j) = false,則表示s[i..]無法匹配p[j..]。
-
根據這個定義,我們想要的答案就是i = 0,j = 0時dp函數的結果,所以可以這樣使用這個dp函數:
確定狀態轉譯方程
- 通配符匹配 0 次或多次
-
將j加 2,i不變,含義就是直接跳過p[j]和之后的通配符,即通配符匹配 0 次:
-
將i加 1,j不變,含義就是p[j]匹配了s[i],但p[j]還可以繼續匹配,即通配符匹配多次的情況:
兩種情況只要有一種可以完成匹配即可,所以對上面兩種情況求或運算 -
常規匹配 1 次
- 由于這個條件分支是無*的常規匹配,那么如果s[i] == p[j],就是i和j分別加一:
- 通配符匹配 0 次
- 類似情況 ,將j加 2,i不變
- 如果沒有*通配符,也無法匹配,那只能說明匹配失敗了:
確定base case
- 一個 base case 是j == p.size()時,按照dp函數的定義,這意味著模式串p已經被匹配完了,那么應該看看文本串s匹配到哪里了,如果s也恰好被匹配完,則說明匹配成功:
- 另一個 base case 是i == s.size()時,按照dp函數的定義,這種情況意味著文本串s已經全部被匹配了,那么是不是只要簡單地檢查一下p是否也匹配完就行了呢?
- 這都是不正確的,此時并不能根據j是否等于p.size()來判斷是否完成匹配,只要p[j..]能夠匹配空串,就可以算完成匹配。比如說s = "a", p = "ab*c*",當i走到s末尾的時候,j并沒有走到p的末尾,但是p依然可以匹配s。
base case:
int m = s.size(), n = p.size();if (i == s.size()) {// 如果能匹配空串,一定是字符和 * 成對兒出現if ((n - j) % 2 == 1) {return false;}// 檢查是否為 x*y*z* 這種形式for (; j + 1 < p.size(); j += 2) {if (p[j + 1] != '*') {return false;}}return true; }完整代碼
class Solution { public:/* 計算 p[j..] 是否匹配 s[i..] */bool dp(string& s, int i, string& p, int j) {int m = s.size(), n = p.size();if (i == s.size()) {// 如果能匹配空串,一定是字符和 * 成對兒出現if ((n - j) % 2 == 1) {return false;}// 檢查是否為 x*y*z* 這種形式for (; j + 1 < p.size(); j += 2) {if (p[j + 1] != '*') {return false;}}return true;}if (s[i] == p[j] || p[j] == '.') {// 匹配if (j < p.size() - 1 && p[j + 1] == '*') {// 通配符匹配 0 次或多次return dp(s, i, p, j + 2)|| dp(s, i + 1, p, j);} else {// 常規匹配 1 次return dp(s, i + 1, p, j + 1);}} else {// 不匹配if (j < p.size() - 1 && p[j + 1] == '*') {// 通配符匹配 0 次return dp(s, i, p, j + 2);} else {// 無法繼續匹配return false;}}}// 動態規劃bool isMatch(string s, string p) {return dp(s, 0, p, 0); } };方法二:Dp table
Dp table的思想基本一致,只不過轉換為了數組而已
明確狀態和選擇
- 動態規劃算法的核心就是「狀態」和「選擇」,「狀態」無非就是i和j兩個指針的位置,「選擇」就是p[j]選擇匹配幾個字符。
- dp[i][j] == true代表字符串s的[0.....i]和字符串p的[0.....j]是匹配的
- dp[i][j] == false代表字符串s的[0.....i]和字符串p的[0.....j]是不匹配的
明確狀態轉移方程
- p的前一個字符和s的前一個字符相等會p的前一個字符是'.'直接轉移
- 如果p的前一個為'*'就需要考慮匹配0次和多次了
確定base case
dp[0][0] = true;for(int i = 1; i <= np; i++){if(i - 2 >= 0 && p[i - 1] == '*' && p[i - 2]){dp[0][i] = dp[0][i - 2];}}完整代碼
class Solution { public:bool isMatch(string s, string p) {int ns = s.size();int np = p.size();if(p.empty()) return s.empty();vector<vector<bool>> dp(ns + 1, vector<bool>(np + 1, false));//初始化為true,因為空和空肯定是匹配的dp[0][0] = true;//這里的base case是初始化*匹配0次的情況for(int i = 1; i <= np; i++){if(i - 2 >= 0 && p[i - 1] == '*' && p[i - 2]){dp[0][i] = dp[0][i - 2];}}//開始狀態轉移for(int i = 1; i <= ns; i++){for(int j = 1; j <= np; j++){//p的前一個字符和s的前一個字符相等會p的前一個字符是'.'直接轉移if(p[j - 1] == s[i - 1] || p[j - 1] == '.')dp[i][j] = dp[i - 1][j - 1];//如果p的前一個為'*'就需要考慮匹配0次和多次了if(p[j - 1] == '*'){bool zero, one;if(j - 2 >= 0){//匹配0次zero = dp[i][j - 2];//匹配多次one = (p[j - 2] == s[i - 1] || p[j - 2] == '.') && dp[i - 1][j];dp[i][j] = zero || one;}}}}return dp[ns][np];} };總結
以上是生活随笔為你收集整理的力扣- -正则表达式匹配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL三大日志及主从复制的原理
- 下一篇: 力扣- -阶乘函数后K个零