日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

动态规划之正则表达

發(fā)布時(shí)間:2024/4/11 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划之正则表达 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

動(dòng)態(tài)規(guī)劃之正則表達(dá)

給你一個(gè)字符串 s 和一個(gè)字符規(guī)律 p,請(qǐng)你來實(shí)現(xiàn)一個(gè)支持 ‘.’ 和 ‘*’ 的正則表達(dá)式匹配。

  • ‘.’ 匹配任意單個(gè)字符
  • ‘*’ 匹配零個(gè)或多個(gè)前面的那一個(gè)元素

所謂匹配,是要涵蓋 整個(gè) 字符串 s的,而不是部分字符串。

說明:

s 可能為空,且只包含從 a-z 的小寫字母。 p 可能為空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。

示例 1:

輸入: s = "aa" p = "a" 輸出: false 解釋: "a" 無法匹配 "aa" 整個(gè)字符串。

示例 2:

輸入: s = "aa" p = "a*" 輸出: true 解釋: 因?yàn)?'*' 代表可以匹配零個(gè)或多個(gè)前面的那一個(gè)元素, 在這里前面的元素就是 'a'。因此,字符串 "aa" 可被視為 'a' 重復(fù)了一次。

示例 3:

輸入: s = "ab" p = ".*" 輸出: true 解釋: ".*" 表示可匹配零個(gè)或多個(gè)('*')任意字符('.')。

示例 4:

輸入: s = "aab" p = "c*a*b" 輸出: true 解釋: 因?yàn)?'*' 表示零個(gè)或多個(gè),這里 'c' 為 0 個(gè), 'a' 被重復(fù)一次。因此可以匹配字符串 "aab"。

示例 5:

輸入: s = "mississippi" p = "mis*is*p*." 輸出: false

再次解決這道題!!!!!

方法一:DP table

在進(jìn)行下面的處理之前,需要明確動(dòng)態(tài)規(guī)劃的幾個(gè)步驟:

狀態(tài)

首先狀態(tài) dp 一定能自己想出來。
dp[i][j] 表示 s 的前 i 個(gè)是否能被 p 的前 j 個(gè)匹配

轉(zhuǎn)移方程

怎么想轉(zhuǎn)移方程?首先想的時(shí)候從已經(jīng)求出了 dp[i-1][j-1] 入手,再加上已知 s[i]、p[j],要想的問題就是怎么去求 dp[i][j]。

已知 dp[i-1][j-1] 意思就是前面子串都匹配上了,不知道新的一位的情況。
那就分情況考慮,所以對(duì)于新的一位 p[j] s[i] 的值不同,要分情況討論:

  • 考慮最簡(jiǎn)單的 p[j] == s[i] : dp[i][j] = dp[i-1][j-1]
    然后從 p[j] 可能的情況來考慮,讓 p[j]=各種能等于的東西。

  • p[j] == "." : dp[i][j] = dp[i-1][j-1]

  • p[j] ==" * ":

  • 前兩種情況比較容易解決,接下來就對(duì)幾種情況分別進(jìn)行討論:

    處理「*」 通配符

    第一個(gè)難想出來的點(diǎn):怎么區(qū)分 ‘*’ 的兩種討論情況

    首先給了 ‘*’,明白 ‘*’ 的含義是 匹配零個(gè)或多個(gè)前面的那一個(gè)元素,所以要考慮他前面的元素 p[j-1]。‘*’ 跟著他前一個(gè)字符走,前一個(gè)能匹配上 s[i],‘*’ 才能有用,前一個(gè)都不能匹配上 s[i],‘*’ 也無能為力,只能讓前一個(gè)字符消失,也就是匹配 0 次前一個(gè)字符

    所以按照 p[j-1] 和 s[i] 是否相等,我們分為兩種情況:

  • p[j-1] != s[i] : dp[i][j] = dp[i][j-2]
  • 這就是剛才說的那種前一個(gè)字符匹配不上的情況。

    比如(ab, abc * )。遇到 ‘ * ’ 往前看兩個(gè),發(fā)現(xiàn)前面 s[i] 的 ab 對(duì) p[j-2] 的 ab 能匹配,雖然后面是 ‘c*’,但是可以看做匹配 0 次 c,相當(dāng)于直接去掉 ’c * ‘,所以也是 true。注意 (ab, abc**) 是 False。

  • p[j-1] == s[i] or p[j-1] == ".":
  • ' * ' 前面那個(gè)字符,能匹配 s[i],或者 '*' 前面那個(gè)字符是萬能的 .因?yàn)?‘. *’ 就相當(dāng)于 ‘. .’,那就只要看前面可不可以匹配就行。

    比如 (##b , ###b *),或者 ( ##b , ### . * ) 只看 ### 后面一定是能夠匹配上的。所以要看 b 和 ‘b *’ 前面那部分 ‘##’ 的地方匹不匹配。

    第二個(gè)難想出來的點(diǎn):怎么判斷前面是否匹配

    dp[i][j] = dp[i-1][j] // 多個(gè)字符匹配的情況 or dp[i][j] = dp[i][j-1] // 單個(gè)字符匹配的情況 or dp[i][j] = dp[i][j-2] // 沒有匹配的情況

    看 ### 匹不匹配,不是直接只看 ### 匹不匹配,要綜合后面的 b* 來分析這三種情況是 or 的關(guān)系,滿足任意一種都可以匹配上,同時(shí)是最難以理解的地方:

    dp[i-1][j] 就是看 s 里 b 多不多, ### 和 ###b * 是否匹配,一旦匹配,s 后面再添個(gè) b 也不影響,因?yàn)橛?* 在,也就是 ###b 和 ###b *也會(huì)匹配。

    dp[i][j-1] 就是去掉 * 的那部分,###b 和 ###b 是否匹配,比如 qqb qqb

    dp[i][j-2] 就是 去掉多余的 b *,p 本身之前的能否匹配,###b 和 ### 是否匹配,比如 qqb qqbb* 之前的 qqb qqb 就可以匹配,那多了的 b * 也無所謂,因?yàn)?b * 可以是匹配 00 次 b,相當(dāng)于 b * 可以直接去掉了。

    三種滿足一種就能匹配上。

    為什么沒有 dp[i-1][j-2] 的情況? 就是 ### 和 ### 是否匹配?因?yàn)檫@種情況已經(jīng)是 dp[i][j-1] 的子問題。也就是 s[i]==p[j-1],則 dp[i-1][j-2]=dp[i][j-1]。

    最后來個(gè)歸納

    如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];如果 p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];如果 p.charAt(j) == '*':如果 p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2]如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':dp[i][j] = dp[i-1][j] or dp[i][j] = dp[i][j-1] or dp[i][j] = dp[i][j-2]

    完整代碼:

    public boolean isMatch(String s,String p){if (s == null || p == null) {return false;}boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];dp[0][0] = true;//dp[i][j] 表示 s 的前 i 個(gè)是否能被 p 的前 j 個(gè)匹配for (int i = 0; i < p.length(); i++) { // here's the p's length, not s'sif (p.charAt(i) == '*' && dp[0][i - 1]) {dp[0][i + 1] = true; // here's y axis should be i+1}}for (int i = 0; i < s.length(); i++) {for (int j = 0; j < p.length(); j++) {if (p.charAt(j) == '.' || p.charAt(j) == s.charAt(i)) {//如果是任意元素 或者是對(duì)于元素匹配dp[i + 1][j + 1] = dp[i][j];}if (p.charAt(j) == '*') {if (p.charAt(j - 1) != s.charAt(i) && p.charAt(j - 1) != '.') {//如果前一個(gè)元素不匹配 且不為任意元素dp[i + 1][j + 1] = dp[i + 1][j - 1];} else {dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1] || dp[i + 1][j - 1]);/*dp[i][j] = dp[i-1][j] // 多個(gè)字符匹配的情況 or dp[i][j] = dp[i][j-1] // 單個(gè)字符匹配的情況or dp[i][j] = dp[i][j-2] // 沒有匹配的情況*/}}}}return dp[s.length()][p.length()];}

    C++代碼:

    class Solution { public:// 動(dòng)態(tài)規(guī)劃bool isMatch(string s, string p) {int ns = s.size();int np = p.size();if(p.empty()) //注意這里判斷的寫法,不能是if(p.empty() || s.empty());原因試一下就知道啦return s.empty();vector<vector<bool>> dp(ns+1, vector<bool>(np+1, false));//就是博客中的問題,怎么判斷前面是否匹配,相當(dāng)于提前把遇到‘*’的情況在dp[i][j - 2]//的情況先記錄下來,后面使用,因?yàn)槌跏蓟臅r(shí)候全部都是false,所以要提前處理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];}}for(int i = 1; i <= ns; i++){for(int j = 1; j <= np; j++){if(p[j-1] == s[i-1] || p[j-1] == '.')dp[i][j] = dp[i-1][j-1];//萬金油,直接相等if(p[j-1] == '*'){bool zero, one;if(j-2 >= 0){zero = dp[i][j-2];//匹配0次,one = (p[j-2] == s[i-1] || p[j-2] == '.') && dp[i-1][j];//匹配1次dp[i][j] = zero || one;//有一個(gè)為真即可}}}}return dp[ns][np];}};

    方法二: 動(dòng)態(tài)規(guī)劃

    第?步, 我們暫時(shí)不管正則符號(hào), 如果是兩個(gè)普通的字符串進(jìn)??較, 如何
    進(jìn)?匹配? 我想這個(gè)算法應(yīng)該誰都會(huì)寫:

    bool isMatch(string text, string pattern) {if (text.size() != pattern.size())return false;for (int j = 0; j < pattern.size(); j++) {if (pattern[j] != text[j])return false;} return true; }

    然后, 稍微改造?下上?的代碼, 略微復(fù)雜了?點(diǎn), 但意思還是?樣的,很容易理解吧:

    bool isMatch(string text, string pattern) {int i = 0; // text 的索引位置int j = 0; // pattern 的索引位置while (j < pattern.size()) {if (i >= text.size())return false;if (pattern[j++] != text[i++])return false;} // 相等則說明完成匹配return j == text.size(); }

    如上改寫, 是為了將這個(gè)算法改造成遞歸算法(偽碼) :

    def isMatch(text, pattern) -> bool:if pattern is empty: return (text is empty?)first_match = (text not empty) and pattern[0] == text[0]return first_match and isMatch(text[1:], pattern[1:])

    處理點(diǎn)號(hào)「.」 通配符

    點(diǎn)號(hào)可以匹配任意?個(gè)字符, 萬?油嘛, 其實(shí)是最簡(jiǎn)單的, 稍加改造即可:

    def isMatch(text, pattern) -> bool:if not pattern: return not textfirst_match = bool(text) and pattern[0] in {text[0], '.'}return first_match and isMatch(text[1:], pattern[1:])

    處理「*」 通配符

    星號(hào)通配符可以讓前?個(gè)字符重復(fù)任意次數(shù), 包括零次。 那到底是重復(fù)?次
    呢? 這似乎有點(diǎn)困難, 不過不要著急, 我們起碼可以把框架的搭建再進(jìn)?
    步:

    def isMatch(text, pattern) -> bool:if not pattern: return not textfirst_match = bool(text) and pattern[0] in {text[0], '.'}if len(pattern) >= 2 and pattern[1] == '*':# 發(fā)現(xiàn) '*' 通配符else:return first_match and isMatch(text[1:], pattern[1:])

    星號(hào)前?的那個(gè)字符到底要重復(fù)?次呢? 這需要計(jì)算機(jī)暴?窮舉來算, 假設(shè)
    重復(fù) N 次吧。 前?多次強(qiáng)調(diào)過, 寫遞歸的技巧是管好當(dāng)下, 之后的事拋給遞歸。 具體到這?, 不管 N 是多少, 當(dāng)前的選擇只有兩個(gè): 匹配 0 次、 匹
    配 1 次。 所以可以這樣處理:

    if len(pattern) >= 2 and pattern[1] == '*':return isMatch(text, pattern[2:]) or \first_match and isMatch(text[1:], pattern)# 解釋: 如果發(fā)現(xiàn)有字符和 '*' 結(jié)合,# 或者匹配該字符 0 次, 然后跳過該字符和 '*'# 或者當(dāng) pattern[0] 和 text[0] 匹配后, 移動(dòng) text

    可以看到, 我們是通過保留 pattern 中的「*」 , 同時(shí)向后推移 text, 來實(shí)現(xiàn)
    「」 將字符重復(fù)匹配多次的功能
    。 舉個(gè)簡(jiǎn)單的例?就能理解這個(gè)邏輯了。 假
    設(shè) pattern = a , text = aaa, 畫個(gè)圖看看匹配過程:

    選擇使?「?jìng)渫洝?遞歸的?法來降低復(fù)雜度

    我將暴?解法和優(yōu)化解法放在?起, ?便你對(duì)?,

    # 帶備忘錄的遞歸 def isMatch(text, pattern) -> bool:memo = dict() # 備忘錄def dp(i, j):if (i, j) in memo: return memo[(i, j)]if j == len(pattern): return i == len(text)first = i < len(text) and pattern[j] in {text[i], '.'}if j <= len(pattern) - 2 and pattern[j + 1] == '*':ans = dp(i, j + 2) or \first and dp(i + 1, j)else:ans = first and dp(i + 1, j + 1)memo[(i, j)] = ansreturn ansreturn dp(0, 0)# 暴?遞歸 def isMatch(text, pattern) -> bool:if not pattern: return not textfirst = bool(text) and pattern[0] in {text[0], '.'}if len(pattern) >= 2 and pattern[1] == '*':return isMatch(text, pattern[2:]) or \first and isMatch(text[1:], pattern)else:return first and isMatch(text[1:], pattern[1:])

    總結(jié)

    以上是生活随笔為你收集整理的动态规划之正则表达的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。