困难动态规划系列、经典的正则表达式和通配符匹配问题(难题)
| 2020/10/24、 周六、今天又是奮斗的一天。 |
正則表達式(Regular Expression, RE)就是一組定義某種搜索模式(pattern)的字符。
文章目錄
- Leetcode 10 正則表達式匹配
- Leetcode 44 通配符匹配
Leetcode 10 正則表達式匹配
題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/regular-expression-matching/
* : 前一個字符匹配0次或任意多次 . : 匹配除了換行符以外任意一個字符示例 1 輸入:s = "aa", p = "a" 輸出:false 示例 2 輸入:s = "aa", p = "a*" 輸出:true 示例 3 輸入:s = "ab", p = ".*" 輸出:true當然你可以直接在程序里面調用正則表達式的庫?但你這樣面試絕對過不了的
我覺得應該想一想,p的子串和s的子串有沒有聯系?當然有!
大家可以先看下Leetcode官方題解
我們先定義一個二維數組dp,其中dp[i][j]表示的是p的前j個字符和s的前i個字符匹配的結果。
取二維數組dp[i][j],表示當s[i]至p[j]為止是否匹配
若dp[i][j]匹配則前一位即dp[i-1][j-1]一定匹配
當前兩個字符是否匹配,取決于兩個字符是否相等或者模式串p中的對應字符是否為單字符匹配通配符「.」
因此s[i] == p[j] or p[j] == '.',很容易得到 dp[i][j] = dp[i-1][j-1] = True
處理特殊情況,當前模式串的對應字符為*時=,如果星號前一個字符匹配不上p[j-1] != s[i]:,星號匹配了 0 次,應忽略這兩個字符,看p[j-2]和 s[i] 是否匹配。 這時 dp[i][j] = dp[i][j-2]
如果匹配成功或者*的前面是.,也就是p[j-1] == s[i] or p[j-1] == '.':,
比如 abbb 和 ab*,那么dp[i][j] = dp[i-1][j],如果下一次繼續遇到,則一直回溯下去。
相反出現了abbb和a.*,同樣dp[i][j] = dp[i-1][j]。
這里會出現一個Bug,就是ab 和 abb*。我們一定認為s比p長嗎?遇到這種情況,就是 dp[i][j] = dp[i][j-2],直接去除.*。
動態規劃最重要的是寫出轉移方程,這里,直接給出結果,這個題目中,難點就在于處理 . 和*兩個符號。
class Solution:def isMatch(self, s: str, p: str) -> bool:m, n = len(s), len(p)# 配對s[i]和s[j]def matches(i, j):if i == 0:return Falseif p[j - 1] == '.':return Truereturn s[i - 1] == p[j - 1]dp = [[False] * (n + 1) for _ in range(m + 1)]dp[0][0] = Truefor i in range(m + 1):for j in range(1, n + 1):if p[j - 1] == "*":dp[i][j] = dp[i][j] | dp[i][j - 2]if matches(i, j - 1):dp[i][j] = dp[i][j] | dp[i - 1][j]else:if matches(i, j):dp[i][j] = dp[i][j] | dp[i - 1][j - 1]return dp[m][n]下面是在Leetcode中逛見的簡單高效的代碼。原則在是在遞歸代碼上進行優化。
class Solution:def isMatch(self, s: str, p: str) -> bool:memo = dict()len_s = len(s)len_p = len(p)def dp(i, j):# s從i起,p從j起,是否匹配if (i, j) in memo: return memo[(i,j)]if j == len_p: return i == len_sfirst_match = i < len_s and p[j] in {s[i], '.'}if j <= len_p - 2 and p[j+1] == '*':# 遇到*, 不匹配,p往后s不動;匹配,s往后p不動memo[(i, j)] = dp(i, j+2) or \(first_match and dp(i+1, j))else:memo[(i, j)] = first_match and dp(i+1, j+1)return memo[(i,j)]return dp(0,0)在上面動態規劃,是將下面的暴力解的方法中,頻繁使用切片操作,復雜度高。在暴力解的基礎上,使用動態規劃的方法,定義變量 i,j 來記錄當前匹配到的位置,用 dp(i, j) 表示 s[i:] 和 p[j:] 是否能夠匹配,避免頻繁切片。這里也引入備忘錄的概念,用來避免重復的運算。
class Solution:def isMatch(self, s: str, p: str) -> bool:if not p:return not s# 解決.first_match = bool(s) and p[0] in {s[0], '.'}# 解決*if len(p) >= 2 and p[1]=="*":return self.isMatch(s, p[2:]) or first_match and self.isMatch(s[1:], p)else:return first_match and self.isMatch(s[1:], p[1:])Leetcode 44 通配符匹配
題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/wildcard-matching/
給定一個字符串 (s) 和一個字符模式 § ,實現一個支持 ‘?’ 和 ‘*’ 的通配符匹配。
'?' 可以匹配任何單個字符。 '*' 可以匹配任意字符串(包括空字符串)。輸入: s = "aa" p = "a" 輸出: false 輸入: s = "aa" p = "*" 輸出: true 輸入: s = "adceb" p = "*a*b" 輸出: true 輸入: s = "acdcb" p = "a*c?b" 輸出: false兩個字符串在一起的題目,其他都不要想,直接考慮動態規劃,而且是字符串匹配題型。首先是構造動態規劃轉移方程。
dp[ i ][ j ]表示s的前i位和p的前j位是否匹配。那么轉移方程首先考慮的是s的第i位和p的第j位的取值情況,當然這里的s都是字母,關鍵就是p的第j位,p的第j位可以是字母可以是?,可以是*。轉移方程如下所示:(s[ i ]表示s的第i位的字母)
if ( s[ i ] == p[ j ]) 那么dp[ i ][ j ] = dp[ i - 1 ][ j - 1 ]if ( s[ i ] != p[ j ])p[ j ]是字母 :dp[ i ][ j ] = false;p[ j ]是?:dp[ i ][ j ] = dp[ i - 1 ][ j - 1 ]p[ j ]是* :dp[ i ][ j ] = dp[ i ][ j - 1 ] || dp[ m ][ j - 1 ] m從[ 1 , i - 1]遍歷這里就是*的概念不同,上面是配對前一個字符匹配0次或任意多次,而這里是可以匹配任意字符串(包括空字符串)
最關鍵是p的第j位是*時,*能匹配任意字符串,空字符串也能匹配,所以此時應該遍歷*匹配s的前面所有情況,也就是*能匹配空字符串,能匹配s的后1位,后2位…甚至匹配整個s字符串,這里為了提高運算效率,只要dp[ i ][ j ]為true就停止遍歷。
這題相比第10題正則匹配式,簡單些。那題分析起來比較麻煩。
class Solution:def isMatch(self, s: str, p: str) -> bool:m, n = len(s), len(p)dp = [[False] * (n + 1) for _ in range(m + 1)]dp[0][0] = Truefor i in range(1, n + 1):if p[i - 1] == '*':dp[0][i] = Trueelse:breakfor i in range(1, m + 1):for j in range(1, n + 1):if p[j - 1] == '*':dp[i][j] = dp[i][j - 1] or dp[i - 1][j]elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:dp[i][j] = dp[i - 1][j - 1] return dp[m][n]| 請一鍵三連!!!!! |
總結
以上是生活随笔為你收集整理的困难动态规划系列、经典的正则表达式和通配符匹配问题(难题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 土豆炖茄子家常做法?
- 下一篇: 二十四、深入Python多进程multi