從左到右依次遍歷型
1 要觀察出正方形的邊長取決於三個因素,上邊,左邊,對角線的正方形邊長的最小值,寫出動態規划方程是關鍵,注意matrix里面的數據類型是字符串,不是數字0 1,所以不能直接用if判斷,用if只要字符串不空就為真,
2 同上一題,容易觀察出以dp[i][j]為右下端點的最大正方形的邊長也是其正方形的個數,
3 這個題無論用什么動態規划方法都要從四個方向遍歷四次,每個位置的最大值是四個方向中的最小值,最后再求最大值,對於數組查找不方便時,可以先將其變為集合,注意set內的元素必須是不可變對象,所以不能是數組,要把數組變為tuple再放入set中,或者可以直接建一個二維數組來記錄0 1
下面是非常相似的一類題型,都是處理兩個字符串的問題,定義dp的時候行和列長度都要加1,因為要做初始化,狀態轉移方程不好寫時可以舉例子來寫,注意初始化加了一列一行后,后面遍歷時的索引是從1到row+1和從1到col+1,
4 高頻面試題,和編輯距離類似,關鍵是寫出狀態轉移方程,子序列問題往往可以用動態規划,這個題可以用兩個數組互相賦值記錄數據來降低空間復雜度,
dp[i][j]= dp[i−1][j−1]+1 s1[i]==s2[j]
max(dp[i−1][j],dp[i][j−1]) s1[i]!= s2[j]
和最長公共子序列實際上是同一個題,
思路同上,dp[i][j]為公共子序列的最大和,先求出所有字符的和,再減去公共的字符之和乘2,或者可以直接定義dp[i][j]為最小的刪除和,只不過初始化的時候不為0,下面為計算最大公共子序列字符之和的動態轉移方程:
dp[i][j]=dp[i−1][j−1] + ord(s1[col-1]) 如果當前字符相等

class Solution: def minimumDeleteSum(self, s1: str, s2: str) -> int: l1 = len(s1) l2 = len(s2) dp = [[0] * (l1+1) for _ in range(l2+1)] for i in range(1, l1+1): dp[0][i] += (dp[0][i-1]+ord(s1[i-1])) for i in range(1, l2+1): dp[i][0] += (dp[i-1][0]+ord(s2[i-1])) for col in range(1, l1+1): for row in range(1, l2+1): if s1[col-1] == s2[row-1]: dp[row][col] = dp[row-1][col-1] else: dp[row][col] = min(dp[row][col-1]+ord(s1[col-1]), dp[row-1][col]+ord(s2[row-1])) return dp[-1][-1]
可以直接用最長公共子序列的方法解,也可以直接定義dp[row][col]為word1[:row-1]到word2[:col-1]的最少的刪除次數,
if word2[row - 1] == word1[col - 1]: dp[row][col] = dp[row - 1][col - 1] else: dp[row][col] = min(dp[row][col - 1], dp[row - 1][col]) + 1
6 由於這個題是要求子數組(暗含連續),所以只有相等時才計算,且相等時直接用左上角的長度加1,dp[i][j]表示A[:i] B[:j] 兩個數組中以A[i] B[j] 結尾的最長的連續子數組的長度,所以每次都計算最大值,這是有dp方程的定義決定的,
7 dp[row][col]表示t[:row-1]在s[:col-1]中的個數,所以當t[row-1]=s[col-1]時,這時dp[row][col] = dp[row][col - 1] + dp[row - 1][col - 1],即左邊的加左上的,當不相等時有 dp[row][col] = dp[row][col - 1],即直接等於左邊的,注意初始化時,空字符串包含於任意的字符串中,所以第一行為1,
8 逐行遍歷中的難題,首先要確定要初始狀態,即什么時候空字符可以匹配p,其次寫狀態轉移方程的時候要考慮到 a*和.*的三種情況,即空字符,a,多個a 三種情況,不可漏掉任何一種,

# 自己的代碼 兩個半小時 # 首先讀懂題意,題中的匹配是指兩個字符串可以相等,單獨的一個‘.'不能表示空字符,只有’*‘前面有’.‘或字符才能構成空字符, # 所以‘*’必須是從第二個開始,它實際上是用作乘, # dp[row][col]表示s[:row]是否可以與p[:col]相等,如果相等為1,否則為0 class Solution: def isMatch(self, s: str, p: str) -> bool: ls = len(s) lp = len(p) dp = [[0] * (lp+1) for _ in range(ls+1)] dp[0][0] = 1 k = [] # 這里必須初始化,dp[0][col]表示空字符串與p[:col]是否匹配, # for i in range(lp): # # 如果當前的‘*’前面只有一個字符,則可以為空,賦值為1, # if p[i] == '*' and len(k) == 1: # k.pop() # dp[0][i+1] = 1 # # 下面這個是多余的,沒必要 # # elif p[i] == '.' and len(k) == 0 and (i+1)<lp and p[i+1] == '*': # # dp[0][i+1] = 1 # # k.append(p[i]) # else: # k.append(p[i]) # 初始化簡寫后 # 如果當前字符為‘*’,則和它的前一個字符必定可以構成空字符串,所以當前狀態就等於p[i-2]的狀態, for i in range(lp): if p[i] == '*' and dp[0][i-1]: dp[0][i+1] = 1 for row in range(1, ls+1): for col in range(1, lp+1): # 如果當前字符為’.’(必定可以匹配,因為它可以匹配任意字符)或相等,則直接等於左上的值 if p[col-1] == '.' or s[row-1] == p[col-1]: dp[row][col] = dp[row-1][col-1] elif p[col-1] == '*': # 這里實際上是a* 的三種情況, # dp[row][col - 2] a* 為空 # dp[row][col - 1] a* 為單個字符 a # dp[row - 1][col - 1] 或 dp[row-1][col] a* 為多個字符 aa或者更多, if p[col-2] == s[row-1]: dp[row][col] = dp[row-1][col] or dp[row][col-2] or dp[row][col-1] # 如果前一個是點,則它必定可以和s[row-1]進行匹配,所以s[row-1]可有可無,當前的情況是否匹配決定與前面的情況 # dp[row - 1][col]表示的是s[row-2]和p[col-1]是否匹配,如果匹配,則加上s[row-1]也必定匹配,這時的 點* 為當個字符s[row-1] # dp[row][col-2]表示的是s[row-1]和p[col-3]是否匹配,這時的 點* 為空字符, # 這里之所以沒有dp[row][col-1]是因為,當dp[row][col-1]為真的時候,dp[row-1][col] or dp[row][col-2]必定為真,所以可以省略, elif p[col-2] == '.': dp[row][col] = dp[row-1][col] or dp[row][col-2] # 最后是兩個不等的情況,這個時候要相匹配 a* 只能為空,所以直接等於dp[row][col-2] else: dp[row][col] = dp[row][col-2] # # 上面的三種情況可以合並成兩種, # for row in range(1, ls + 1): # for col in range(1, lp + 1): # if p[col - 1] == '.' or s[row - 1] == p[col - 1]: # dp[row][col] = dp[row - 1][col - 1] # elif p[col - 1] == '*': # # 二者相等時有三種情況 # # a*作為: 空字符, 單字符 a, 多字符 aaa... # if p[col - 2] == s[row - 1] or p[col - 2] == '.': # dp[row][col] = dp[row - 1][col] or dp[row][col - 2] or dp[row][col - 1] # else: # dp[row][col] = dp[row][col - 2] print(dp) return bool(dp[-1][-1])
這道題用動態規划不是最優解,和上一題類似,寫動態轉移方程的時候務必要考慮到 * 號的三種情況,這樣才能不漏掉任何一種情況,
沿主對角線斜遍歷型
1 與上面的1143題的狀態轉移方程類似,只不過遍歷方式不同,
2 子串是連續的,子序列不連續,這個題的關鍵是初始狀態的確定,主對角線和主對角線下面的是1,因為對於長度是2的字符串也要判斷,row+1,col-1后就會到了主對角線下面,或者是先初始化主對角線和長度為2的(即主對角線上的),再進行遍歷

# 自己的代碼, class Solution: def longestPalindrome(self, s: str) -> str: l = len(s) dp = [[1] * l for _ in range(l)] # for i in range(l): # dp[i][i] = 1 res = [0,0] for col in range(1, l): for row in range(l-col): # 這里最巧妙,開始直接全賦值為1,這里再賦值為0,保證了上三角全是0,下三角全是1 dp[row][col] = 0 if s[row] == s[col] and dp[row+1][col-1]: dp[row][col] = 1 if res[1]-res[0] < col-row: res = [row,col] col += 1 return s[res[0]:res[1]+1]