今天是紀錄LeetCode解題的第十天
來看前十題當中的第二題困難題
第十題題目:Given an input string s and a pattern p, implement regular expression matching with support for ' . ' and ' * ' where:
The matching should cover the entire input string (not partial).
給定一個字串s和欲匹配字串p,完成正則表達式匹配,其中' . '匹配任意字元,' * '匹配零個或多個前一字元
字串應完全匹配,不是部分匹配
動態規劃是一種把大問題拆解成小問題來解的方法,且把已經算過的小問題結果紀錄下來,避免重複計算
簡單來說,動態規劃 = 把大問題拆成小問題 → 記住小問題結果 → 用小問題結果算大問題
我們先來了解動態規劃的概念,以最簡單的費波那契數列為例
題目:求第n個費波那契數
我們知道費波那契數列的第0個和第1個元素分別為0、1,而第n個費波那契數等於前兩個數相加(第n-1、n-2個數)
程式碼如下
def fib(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[0],dp[1] = 0,1
for i in range(2,n + 1)
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fib(10)) #55
這題是DP入門題,因為只需要記住目前狀態等於前兩個狀態的和
題目:爬n階樓梯,每次只能爬一階或兩階,有多少種不同的方法可以爬到n階
假設n=3,表示要爬到第三階,那麼方法就有1+1+1、1+2、2+1共三種方法,這題跟費波那契數列是一樣的概念,只是意義不同,爬到第三階的方法等於爬到第一階(1)+第二階(方法有1+1、2)的方法數,也就是說第n階=n-1階+n-2階的方法數
程式碼如下
def climbStairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(climbStairs(3)) #3
print(climbStairs(5)) #8
大概了解動態規劃是怎麼樣把問題細分的,我們回到LeetCode第十題
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] = True #空字串匹配空的p
#當s是空字串(i = 0)的時候,如果p的當前位置是*,那麼 p[:j] 可以等於「把前一個字母和*一起刪掉」, 所以就看dp[0][j-2](即p去掉任意字母和*之後,能不能匹配空字串)
for j in range(2,n+1):
if p[j-1] == "*":
dp[0][j] = dp[0][j-2]
for i in range(1,m+1):
for j in range(1,n+1):
if p[j-1] == "." or p[j-1] == s[i-1]: #如果目前是.(任意字元)或字母相同代表匹配成功
dp[i][j] = dp[i-1][j-1] #設置成和前一個字元的匹配結果相同
elif p[j-1] == "*":
dp[i][j] = dp[i][j-2] #視為零次
if p[j-2] == "." or p[j-2] == s[i-1]: #如果前一個字元是.或和目前字元相同
dp[i][j] = dp[i][j] or dp[i-1][j] #視為多次,只要零次或多次其中一個匹配就等於匹配成功
return dp[m][n]
i\j | 0 (空) | 1 (c) | 2 (*) | 3 (a) | 4 (*) | 5 (b) |
---|---|---|---|---|---|---|
0 | T | F | T | F | T | F |
1 (a) | F | F | F | T | T | F |
2 (aa) | F | F | F | F | T | F |
3 (aab) | F | F | F | F | F | T |
我們來看這個表格代表什麼意思
在這個表格當中,當i=0、j=0時,表示s=' '、p=' ',所以兩者匹配
i=0、j=2:表示' '要去匹配'c*'(因為c可以匹配零個或多個,所以為True)
i=2、j=3:表示'aa'要去匹配'c(星號)a(這裡的a只能匹配一個a,但s有兩個a所以不能匹配為False)
i=1(s[0] = 'a')
i=2(s[1] = 'a')
i=3(s[2] = 'b')
這題的關鍵在於動態規劃 (DP),把「字串前綴是否能匹配」拆成小問題:
dp[i][j] = True 代表 s 的前 i 個字元 與 p 的前 j 個字元可以匹配
還有需要注意的點是' * '的兩種可能性,它可以是「沒有出現」,也可以代表「連續出現多次」,因此在DP表格裡會同時考慮「左兩格」與「上面一格」,並用 or 連接
最後,這題也可以充分了解到DP表格不是機械公式,而是把規則一步步轉換成邏輯關係的過程