Leetcode第10題 正則表示式匹配 C++動態規劃解法
阿新 • • 發佈:2021-02-04
技術標籤:# leetcodeg回顧動態規劃leetcode動態規劃字串正則表示式
這道題難為了我快一天了。看了官方的解法,不得的說,他的解法既巧妙,又複雜。實際上這道題的第五個條件很重要。
5、保證每次出現字元 * 時,前面都匹配到有效的字元。
也就是說不可能出現p字串第一個字元為 * 的情況。
得出動態轉移方程也並不難,但是這個方程裡面是有幾個坑的。而官方解法是採用了matches去避免。
這裡我們就直接上圖說明。
如果p[i]=* , f[i][j]=f[i-1][j] or f[i][j-2] 。這一步大家都能理解,但是坑也就在這裡。
如果s=“c”,p=“a*c”。這種情況如何呢?
如果我們正常的初始化,是對f[0][0]=1進行了初始化,但是外層迴圈卻是從i=0開始,實際上,這裡就出現了f[0][2]=1的情況。
但問題又來了,如 果我們是從0行開始的迴圈,我們對p[i]!= * 的時候,是要進行s[i-1]==p[j-1] || p[j-1] ==’.'的判斷的,現在你讓i從0開始,那不是會越界嗎?所以官方的這個matches及其巧妙。
當然也要配上迴圈使用,我們可以看到,在第0行的時候,如果p的偶數位字元是 * ,那麼將會非常神奇的出現了f[0][2]=f[0][4]…f[0][2n]=1的現象,而因為matches的限制下面的語句,但它的值已經是1了。而對於或者奇數位為 * 的情況則不會發生(你不必擔心第一位是 * ,因為在前提條件5裡面,已經規避了這一情況),,因為只有f[0][0]=1。而對於不為 * 的情況,第0行會直接遮蔽掉下面f[i][j] |= f[i - 1][j - 1]的運算,也不會發生越界的現象。
auto matches = [&](int i, int j) {
if (i == 0) {
return false;
}
if (p[j - 1] == '.') {
return true;
}
return s[i - 1] == p[j - 1];
};
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];
}
如果不用matches呢?當然你就要在某些句子裡附加一個i>0的判斷。我的寫法如下。所以我個人認為這是極蠢的做法。增加了複雜度。
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty())
return s.empty();
int i,j,m=s.size(),n=p.size();
bool dp[m+1][n+1];
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for (i=0;i<=m;++i)
{
for(j=1;j<=n;++j)
if(p[j-1]=='*')
dp[i][j]=(i>0&&(p[j-2]==s[i-1]||p[j-2]=='.'))&&dp[i-1][j]||dp[i][j-2];
else
dp[i][j]=i>0&&(p[j-1]==s[i-1]||p[j-1]=='.')&&dp[i-1][j-1];
}
return dp[m][n];
}
};
而當我看到了下面一種解法的時候,我覺得實在是太妙了!
它在s和p前面加了一個空格,實際上加任何相同字元都可以。這樣就巧妙的解決了s=“c”,p="a*c"出現的越界問題。
而且也解決了前提5裡面的一個隱含條件的顯示,即p的第一個字元不能為 * ,這種解法下,即使p首字元為 * 也可以通過。
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty())
return s.empty();
s=" "+s;
p=" "+p;
int i,j,m=s.size(),n=p.size();
bool dp[m+1][n+1];
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for (i=1;i<=m;++i)
{
for(j=1;j<=n;++j)
{if(p[j-1]=='*')
dp[i][j]=(p[j-2]==s[i-1]||p[j-2]=='.')&&dp[i-1][j]||dp[i][j-2];
else
dp[i][j]=(p[j-1]==s[i-1]||p[j-1]=='.')&&dp[i-1][j-1];
}
}
return dp[m][n];
}
};
那麼有沒有不借用這種的操作呢?答案是有的,使用帶備忘錄的動態規劃即可。
這裡先空著不談。
以上是自底向上,當然也可以自頂向底來實現。但同樣需要考慮陣列越界的情況了。這可太男了~
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty())
return s.empty();
int i,j,m=s.size(),n=p.size();
bool dp[m+1][n+1];
memset(dp,0,sizeof(dp));
dp[m][n]=1;
for (i=m;i>=0;--i)
{
for(j=n-1;j>=0;--j)
{bool first=i<m&&(p[j]==s[i]||p[j]=='.');
if(j+1<n&&p[j+1]=='*')
dp[i][j]=dp[i][j+2]||first&&dp[i+1][j];
else
dp[i][j]=first&&dp[i+1][j+1];
}
}
return dp[0][0];
}
};