二維動態規划


從左到右依次遍歷型

1 要觀察出正方形的邊長取決於三個因素,上邊,左邊,對角線的正方形邊長的最小值,寫出動態規划方程是關鍵,注意matrix里面的數據類型是字符串,不是數字0 1,所以不能直接用if判斷,用if只要字符串不空就為真,

221. 最大正方形

2 同上一題,容易觀察出以dp[i][j]為右下端點的最大正方形的邊長也是其正方形的個數,

1277. 統計全為 1 的正方形子矩陣

3 這個題無論用什么動態規划方法都要從四個方向遍歷四次,每個位置的最大值是四個方向中的最小值,最后再求最大值,對於數組查找不方便時,可以先將其變為集合,注意set內的元素必須是不可變對象,所以不能是數組,要把數組變為tuple再放入set中,或者可以直接建一個二維數組來記錄0 1

764. 最大加號標志

下面是非常相似的一類題型,都是處理兩個字符串的問題,定義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]​

1143. 最長公共子序列

和最長公共子序列實際上是同一個題,

1035. 不相交的線

思路同上,dp[i][j]為公共子序列的最大和,先求出所有字符的和,再減去公共的字符之和乘2,或者可以直接定義dp[i][j]為最小的刪除和,只不過初始化的時候不為0,下面為計算最大公共子序列字符之和的動態轉移方程:

dp[i][j]=dp[i−1][j−1] + ord(s1[col-1])          如果當前字符相等

dp[row][col] = max(dp[row-1][col], dp[row][col-1])  如果當前字符不相等
或者直接定義dp[i][j]為 s1[i-1]  s2[j-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]
View Code

712. 兩個字符串的最小ASCII刪除和

可以直接用最長公共子序列的方法解,也可以直接定義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

583. 兩個字符串的刪除操作

6 由於這個題是要求子數組(暗含連續),所以只有相等時才計算,且相等時直接用左上角的長度加1,dp[i][j]表示A[:i] B[:j] 兩個數組中以A[i] B[j] 結尾的最長的連續子數組的長度,所以每次都計算最大值,這是有dp方程的定義決定的,

718. 最長重復子數組

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,

115. 不同的子序列

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])
View Code

10. 正則表達式匹配

這道題用動態規划不是最優解,和上一題類似,寫動態轉移方程的時候務必要考慮到 * 號的三種情況,這樣才能不漏掉任何一種情況,

44. 通配符匹配

沿主對角線斜遍歷型

1 與上面的1143題的狀態轉移方程類似,只不過遍歷方式不同,

516. 最長回文子序列

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]
View Code

5. 最長回文子串

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM