- 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。