官方题解的代码比较精炼,确实很难理解,为了能使题主理解,说明如下
对字符串s1,s2 s1+s2表示字符串的连接,用null表示空串 对一个字符c1 和一个.或字符c2 用match(c1,c2)表示它们是否匹配
设要验证匹配与否的字符串为S,长度m+1,设0<=i<=j<=m,则由S在索引位置i和索引位置j之间所有索引位置上的字符(包括索引位置i和索引位置j)构成的子串记为S[i,j],S在索引位置k(0<=k<=m)上的字符记为S[k]
设正则表达式字符串为P,长度为n+1,同理可定义Pi,j和Pk
现对1<=i<=m和1<=j<=n,考察S[0,i]和P[0,j]
如果S[0,i]和P[0,j]匹配那么
如果P[j]=*此时注意P[j-1,j]要么匹配假想的接在S[0,i]之后的空串null要么匹配全部由和P[j-1]匹配的字符组成的S[0,i]的后缀Sk,i
如果match(S[i],P[j-1])=false
此时P[j-1,j]只能匹配假想的接在S[0,i]之后的空串null,又S[0,i]和P[0,j]匹配即S[0,i]+null和P[0,j]匹配,故必有S[0,i]匹配P[0,j-2]
如果match(S[i],P[j-1])=true
同理如果P[j-1,j]匹配假想的接在S[0,i]之后的空串null,则S[0,i]匹配P[0,j-2]
否则P[j-1,j]匹配上文所提S[k,i],由于S[0,i]和P[0,j]匹配即S[0,k-1]+S[k,i]和P[0,j]匹配故必有
S[0,k-1]匹配P[0,j-2] (若S[0,k-1]和P[0,j-2]不存在则视为空串null对证明过程没有影响)
若k = i 则令P[j -1,j]匹配null,则P[0,j-2]+P[j-1,j]匹配S[0,k-1]+null即S[0,i-1]
从而S[0,i-1]匹配P[0,j]
若k<i注意到S[k,i-1]全部由和P[j-1]匹配的字符组成,故有S[k,i-1]匹配P[j-1,j],从而S[0,k-1]+S[k,i-1]匹配P[0,j-2]+P[j-1,j],即S[0,i-1]匹配P[0,j]
如果P[j]!=*,则P[j,j]只匹配单个字符,P[j,j]与S[i,i]严格对应,P[j,j]要么匹配S[i,i]要么不匹配S[i,i],但由假设S[0,i]和P[0,j]匹配故必有P[j,j]匹配Si,i这样很容易得到S[0,i-1]匹配P[0,j-1]
综上我们得到命题一
如果S[0,i]和P[0,j]匹配则
如果P[j]=*则
如果match(S[i],P[j-1])=false则S[0,i]匹配P[0,j-2]
如果match(S[i],P[j-1])=true则S[0,i]匹配P[0,j-2]和S[0,i-1]匹配P[0,j]中必有一个成立
如果P[j]!=*则S[0,i-1]匹配P[0,j-1],P[j,j]匹配S[i,i]
命题一的逆命题命题二同样成立
事实上如果P[j]=*
如果match(S[i],P[j-1])=false则由逆命题假设S[0,i]匹配P[0,j-2] 我们令P[j-1,j]匹配null,则S[0,i]+null匹配P[0,j-2]+P[j-1,j]则S[0,i]匹配P[0,j]
如果match(S[i],P[j-1])=true则由逆命题假设S[0,i]匹配P[0,j-2]和S[0,i-1]匹配P[0,j]中必有一个成立
若S[0,i-1]匹配P[0,j]成立
根据命题一证明过程中的相关内容有
若P[j-1,j]匹配接在S[0,i-1]之后的空串null则S[0,i-1]匹配P[0,j-2],但match(S[i],P[j-1])=true所以S[i,i]匹配P[j-1,j]
因此S[0,i-1]+S[i,i]匹配P[0,j-2]+P[j-1,j]即S[0,i]匹配P[0,j]
若P[j-1,j]匹配S[0,i-1]的后缀Sk,i-1,注意S[0,i-1]匹配P[0,j]因此S[0,k-1]匹配P0,j-2
另外S[k,i-1]全部由和P[j-1]匹配的字符组成而match (S[i],P[j-1])=true因此S[k,i]全部由P[j-1]组成故S[k,i]匹配P[j-1,j]从而
S[0,k-1]+S[k,i]匹配P[0,j-2]+P[j-1,j]故S[0,i]匹配P[0,j]
如果S[0,i]匹配P[0,j-2]成立照搬match(S[i],P[j-1])=false情形的证明同样有S[0,i]匹配P[0,j]
如果P[j]!=*由逆命题假设有S[0,i-1]匹配P[0,j-1],P[j,j]匹配S[i,i]此时显然有S[0,i]匹配P[0,j]
这样就证明了命题二的正确性
综合命题一命题二我们得到命题三
如果P[j]=*则
如果match(S[i],P[j-1])=false 若S[0,i]匹配P[0,j-2]则S[0,i]和P[0,j]匹配否则不匹配
如果match(S[i],P[j-1])=true若S[0,i]匹配P[0,j-2]和S[0,i-1]匹配P[0,j]中必有一个成立则S[0,i]和P[0,j]匹配否则不匹配
如果P[j]!=* 若S[0,i-1]匹配P[0,j-1],P[j,j]匹配S[i,i]则S[0,i]和P[0,j]匹配否则不匹配
如果我们用dp[i][j]表示S[0,i]和P[0,j]是否匹配,为true表示匹配为false表示不匹配则由命题三可得
对1<=i<=m
1<=j<=n
如果P[j]=*则
如果match(S[i],P[j-1])=false dp[i][j] = j > 1 && dp[i][j-2]
如果match(S[i],P[j-1])=true dp[i][j] = (j > 1 && dp[i][j-2]) || dp[i-1][j]
如果P[j]!=* dp[i][j] = dp[i-1][j-1] && match(S[i],P[j])
现在再回过头看官方题解代码,题主应该明白了吧
另外再提一下dp[i][0]1<=i <=m
的计算是很容易的事情 题主可以自己思考
dp[0][j] 0<=j <=n的计算我们可以照葫芦画瓢,仿照命题一命题二的证明方法得到命题四
对1<=j<=n有
P[0,j]匹配S[0,0]当且仅当
如果P[j]=*
如果match(S[0],P[j-1])=true
S[0,0]匹配P[0,j-2] j>1 或null匹配P0,j-2
如果match(S[0],P[j-1])=false
S[0,0]匹配P[0,j-2] j>1
如果P[j]!=*
null匹配P[0,j-1]且match(S[0], P[j]) = true
类似的,我们可以把命题四转换成伪代码
item_larger_than_one = false
pre_has_item = false
pre_pre_has_item = false
for (j from 0 to n)
if (P[j] == *)
if (match(S[0],P[j-1]) == true)
d[0][j]=(j>1 && d[0][j-2]) || pre_pre_has_item == false
else
d[0][j] = j>1 && d[0][j-2]
pre_pre_has_item = pre_has_item
if (item_larger_than_one == false)
pre_has_item = false
else
d[0][j] = pre_has_item == false && match(S[0], P[j]) == true
if (pre_has_item)
item_larger_than_one = true
pre_pre_has_item = pre_has_item
pre_has_item = true
这里pre_has_item指P在当前索引j前的部分是否存在项,项指由.或一个不为的字符组成的不和关联的独立的子匹配表达式,pre_pre_has_item指P在索引j-1前的部分是否存在项 item_larger_than_one指P在索引j前的部分的项的数目是否大于一,事实上,p[0,j]能够匹配空串当且仅当p[0,j]中不含任何项,有了上述说明,pre_has_item,pre_pre_has_item和item_larger_than_one的更新策略题主可以自己体会
另外,尽管命题四j>=1但以上伪代码在j==0时仍然可以正确运行,题主可以自己模拟