二维动态规划

从左到右依次遍历型

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. 最长回文子串

原文地址:https://www.cnblogs.com/xxswkl/p/12859176.html