• 二维动态规划


    从左到右依次遍历型

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

  • 相关阅读:
    PHP函数utf8转gb2312编码
    mysql的数据恢复
    Centos5.6 x86下部署安装DRBD+Heartbeat+MySQL
    使用mysqlproxy 快速实现mysql 集群 读写分离
    删除MySQL二进制日志的3种方法
    mysql proxy 中文乱码解决办法
    有一天……
    占个位子
    雪夜拾到一部破旧的手机
    书教得再好也还是个讲师 学生千篇文悼大学讲师
  • 原文地址:https://www.cnblogs.com/xxswkl/p/12859176.html
Copyright © 2020-2023  润新知