Leetcode Day4 (动态规划I) (很多路径题)

  • python小技巧
  • 好题
    • 221 最大正方形, 非常好的题

爬楼梯和fib跳过了

740 删除并获得点数

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

虽然我是从dp里选出来的题, 但我第一直觉还是很难觉得这个是动态规划

突然开窍是因为如果选了一个元素的话, 那么所有的3都会被选上. 所以当我们统计完每个元素后, 这个问题便转化为打家劫舍了.

class Solution:
    def deleteAndEarn(self, nums: List[int]) -> int:
        count = [0] * (max(nums) + 1)
        for num in nums:
            count[num] += num
        # dp1[i]指选了i的最大点数
        # dp2[i]指没选i的最大点数
        dp1 = [0] * (len(count) + 1)
        dp2 = [0] * (len(count) + 1)
        dp1[0] = count[0]
        dp2[0] = 0
        for i in range(1, len(count)):
            dp1[i] = dp2[i-1] + count[i]
            dp2[i] = max(dp1[i-1], dp2[i-1])
        return max(max(dp1), max(dp2))

62 不同路径

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for i in range(n):
          dp[0][i] = 1
        for i in range(m):
          dp[i][0] = 1
        for i in range(1, m):
          for j in range(1, n):
            dp[i][j] = dp[i -1][j] + dp[i][j-1]
        return dp[m-1][n-1]

这样是最直接的dp算法, 但是我们能不能优化一下空间呢?

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp1 = [1] * n
        for i in range(1, m):
          for j in range(1, n):
            dp1[j] = dp1[j - 1] + dp1[j]
        return dp1[-1]

这样就可以了, 一排一排来
在这里插入图片描述

63 不同路径II(网格中障碍物)

当遇到障碍物时, 把dp那一项设为0就可以了.

有个小坑的地方就是在62中我们把第一行和第一列直接赋值1, 但是这里的情况是遇见障碍物之后的都是0了

64 找一条路径使得花费最小

简单

120 三角形中的64

这个题也简单, 只是得注意边界情况, 很容易错

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        dp = copy.deepcopy(triangle)
        if len(dp) == 1:
            return dp[0][0]
        dp[1][0] = triangle[1][0] + triangle[0][0]
        dp[1][1] = triangle[1][1] + triangle[0][0]
        for i in range(2, len(triangle)):
            for j in range(len(dp[i])):
                if j == 0:
                    dp[i][j] = triangle[i][j] + dp[i-1][j]
                elif j == len(dp[i]) - 1:
                    dp[i][j] = triangle[i][j] + dp[i-1][j-1]
                else:
                    dp[i][j] = triangle[i][j] + min(dp[i-1][j], dp[i-1][j-1])
        return min(dp[-1])

其中的if和elif部分

931 可以往三个地方落的120

class Solution:
    def minFallingPathSum(self, matrix: List[List[int]]) -> int:
        n = len(matrix[0])
        if n <= 1:
            return matrix[0][0]
        dp1 = matrix[0].copy()
        dp2 = [0] * n
        for i in range(1, n):
            dp2[0] = matrix[i][0] + min(dp1[0], dp1[1])
            dp2[-1] = matrix[i][-1] + min(dp1[-1], dp1[-2])
            for j in range(1, n -1):
                dp2[j] = matrix[i][j] + min(dp1[j-1], dp1[j+1], dp1[j])
            dp1 = dp2.copy()
        return min(dp2)

221 最大正方形

很喜欢这种一拿到手无从下手的题

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        # dp[i][j]以dp[i][j]为左上角顶点的最大正方形边长
        m = len(matrix)
        n = len(matrix[0])
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for i in range(m):
            for j in range(n):
                dp[i][j] = self.find(i, j , matrix)
        print(dp)
        return max(max(row) for row in dp) ** 2

    def find(self, i, j, matrix):
        m = len(matrix)
        n = len(matrix[0])
        if matrix[i][j] == 0:
            return 0
        else:
            best = 0
            for k in range(1, min(m,n)):
                if i + k >= m or j + k >= n:
                    break
                can = True
                for x in range(i, i + k + 1):
                    for y in range(j, j + k + 1):
                        if matrix[x][y] == 0:
                            can = False
                if can:
                    best = k
                else:
                    break
            return best

这是我一开的写法, 不管ac没有, 复杂度都是相当之高的.

然后我突然开窍了, 假如dp[i][j]指的是右下角的正方形的最大边长, 那么我的状态转移方程就比较简单了:
对于dp[i][j], 如果dp[i-1][j-1]的数字为2, 那么我只需dp[i][j]向左向上的2个元素都为1就可以加了

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        # dp[i][j]以dp[i][j]为右下顶点的最大正方形边长
        m = len(matrix)
        n = len(matrix[0])
        dp = [[int(num) for num in row] for row in matrix]  # 直接在初始化时转换类型
        
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == '0':  # 确保判断的是字符'0'而不是整数0
                    dp[i][j] = 0
                else:
                    size = dp[i-1][j-1]
                    valid = True
                    for x in range(i-size, i):
                        if dp[x][j] == 0:
                            valid = False
                            break  # 提前退出循环以提高效率
                    for y in range(j - size, j):
                        if dp[i][y] == 0:
                            valid = False
                            break  # 同样提前退出
                    if valid:
                        dp[i][j] = size + 1
        print(dp)
        return max(max(row) for row in dp) ** 2  # 修改了从matrix到dp的返回值

但是这个方法对于一个情景就不对了:
[[“0”,“0”,“0”,“1”],
[“1”,“1”,“0”,“1”],
[“1”,“1”,“1”,“1”],
[“0”,“1”,“1”,“1”],
[“0”,“1”,“1”,“1”]]
跑完后我的dp数组是
[[0, 0, 0, 1],
[1, 1, 0, 1],
[1, 2, 1, 1],
[0, 1, 1, 2],
[0, 1, 2, 2]]
问题就出在(3,2)
那我就把它改称为从dp[i-1][j-1]的值开始尝试:

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        # dp[i][j]以dp[i][j]为右下顶点的最大正方形边长
        m = len(matrix)
        n = len(matrix[0])
        dp = [[int(num) for num in row] for row in matrix]  # 直接在初始化时转换类型
        
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == '0':  # 确保判断的是字符'0'而不是整数0
                    dp[i][j] = 0
                else:
                    s = dp[i-1][j-1]
                    for size in range(s, 0, -1):
                        valid = True
                        for x in range(i-size, i):
                            if dp[x][j] == 0:
                                valid = False
                                break  # 提前退出循环以提高效率
                        for y in range(j - size, j):
                            if dp[i][y] == 0:
                                valid = False
                                break  # 同样提前退出
                        if valid:
                            dp[i][j] = size + 1 
                            break
        print(dp)
        return max(max(row) for row in dp) ** 2  # 修改了从matrix到dp的返回值

结果就比较丑了


正确的做法是:

if dp[i][j] == 1:
	dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
}

我只能说惊为天人

但是如何理解可是要花一点脑筋的:
回到王氏定理, 我们有三个元素, 左, 左上和上(dp[i-1][j], dp[i-1][j-1] and dp[i][j-1])
无论哪边最小, 增加size by 1的所有格子已经被剩余的两个保证了一定是1

用大佬的总结:

简单说一下自己的理解,f(i,j)表示以(i,j)为右下角的正方形的最大边长,如果 (i,j)为“0”,以(i,j)为右下角不可能构成全为“1”的正方形f(i,j)=0,如果(i,j)为“1”,至少可以获得边长为1的正方形,还能不能变大只能向左向上扩展边长,这个时候需要看正上,左边和左上三个点,因为扩展定会将这三个相邻点包含进来,如果三个点中最小值为0,那么扩展后肯定不行,如果最小值为1,那么三个点都为1,定能扩展成边长为2的正方形,同理能扩展到最大的是 min(左,上,左上) + 1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值