算法基础------动态规划(2)网格图DP(基础)
LCR 166. 珠宝的最高价值
常规dp(或者有递推的回溯,贪心等方法)都可以通过分析递归调用树(搜索树)来分析时间复杂度。其中树的层数代表做选择的次数,本题中任意方法选择次数相等,均为m(行数)+n(列数)次,故有m+n层,空间(递归的栈空间)用到m+n。又每次选择的选项仅有两个,故为二叉树,从而我们能计算递归调用次数2^(m+n)。
dp优化方法一般是:
- 时间:备忘录(记忆化搜索)-> 自底向上(递推)
对本题的优化方法:
- 空间:两个数组(滚动数组)->一个数组 ->原地修改
代码如下
1、递推:
dfs函数改为d数组
递归边界改为数组初始值
递归改成循环
python
class Solution:
def jewelleryValue(self, frame: List[List[int]]) -> int:
d=[[0 for i in range(len(frame[0]))] for j in range(len(frame))]
d[0][0]=frame[0][0]
for i in range(1,len(frame)):
d[i][0]=d[i-1][0]+frame[i][0]
for j in range(1,len(frame[0])):
d[0][j]=d[0][j-1]+frame[0][j]
for m in range(1,len(frame)):
for n in range(1,len(frame[0])):
d[m][n]=max(d[m-1][n],d[m][n-1])+frame[m][n]
return d[-1][-1]
空间O(nm)
python
class Solution:
def jewelleryValue(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
f = [[0] * (n + 1) for _ in range(m + 1)]
for i, row in enumerate(grid):
for j, x in enumerate(row):
f[i + 1][j + 1] = max(f[i + 1][j], f[i][j + 1]) + x
return f[m][n]
2、两个数组,滚动数组 空间O(n)
python
class Solution:
def jewelleryValue(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
f = [[0] * (n + 1) for _ in range(2)]
for i, row in enumerate(grid):
for j, x in enumerate(row):
f[(i + 1) % 2][j + 1] = max(f[(i + 1) % 2][j], f[i % 2][j + 1]) + x #交错赋值很妙
return f[m % 2][n]
3、一个数组 空间O(n)
python
class Solution:
def jewelleryValue(self, grid: List[List[int]]) -> int:
n = len(grid[0])
f = [0] * (n + 1)
for row in grid:
for j, x in enumerate(row):
f[j + 1] = max(f[j], f[j + 1]) + x
return f[n]
4、原地修改 空间O(1)
python
class Solution:
def jewelleryValue(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
f = [[0] * (n + 1) for _ in range(2)]
for i, row in enumerate(grid):
for j, x in enumerate(row):
f[(i + 1) % 2][j + 1] = max(f[(i + 1) % 2][j], f[i % 2][j + 1]) + x
return f[m % 2][n]
这些节省空间的思路值得反复观看,原地修改由于没有增加一列初始列,代码相对于一个数组,要复杂一些。
931 下降路径最小和 1573
inf做最大值,增加头尾。pre, f[c + 1] = f[c + 1], min(pre, f[c + 1], f[c + 2]) + x
可以避免使用中间变量。
python
class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
n = len(matrix)
f = [inf] + matrix[0] + [inf]
for row in matrix[1:]:
pre = f[0] # 充当 f[c]
for c, x in enumerate(row):
pre, f[c + 1] = f[c + 1], min(pre, f[c + 1], f[c + 2]) + x
return min(f)
2684 矩阵中移动的最大次数
可以用两个数组记录,can记录当前判断行能被移动到的元素,tes在判断中记录下一行可以移动到的元素。
python
class Solution:
def maxMoves(self, grid: List[List[int]]) -> int:
m,n=len(grid),len(grid[1])
can=[1]*m
tes=[0]*m
ans=0
for i in range(n-1):
for j in range(m):
if can[j]==1:
for k in range(-1,2):
if m>j+k>=0 and grid[j+k][i+1]>grid[j][i]:
tes[j+k]=1
if 1 in tes:
ans+=1
can=tes.copy()
tes=[0]*m
return ans
当然注意到数字都是正数,可以直接在原来数字上乘以负一来标记入队,每列遍历本列入队(标记)的可以移动到的元素,可以节省空间O(1)
python
class Solution:
def maxMoves(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
for row in grid:
row[0] *= -1 # 入队标记
for j in range(n - 1):
for i, row in enumerate(grid):
if row[j] > 0: # 不在队列中
continue
for k in i - 1, i, i + 1:
if 0 <= k < m and grid[k][j + 1] > -row[j]:
grid[k][j + 1] *= -1 # 入队标记
if all(row[j + 1] > 0 for row in grid): # 无法再往右走了
return j
return n - 1
在Python中,all()函数用于判断可迭代对象中的所有元素是否都为真。如果可迭代对象中的所有元素都为真,则返回True,否则返回False。
与all()函数相对应的函数是any()函数。
any()函数用于判断可迭代对象中的任何一个元素是否为真。如果可迭代对象中的任何一个元素为真,则返回True,否则返回False。
1289 下降路径最小和 II 1697
看起来是困难,实际上是简单题,空间优化也特别好做。
若是空间复杂度优化到O(n)
python
class Solution:
def minFallingPathSum(self, grid: List[List[int]]) -> int:
n=len(grid)
# d=[([0]*n)*n] #不可这样写,最后n个嵌套在内的列表索引一个地址
d=[grid[n-1][j] for j in range(n)]
b=[0]*n
for i in range(n-2,-1,-1):
for j in range(n):
b[j]=min(d[m] for m in range(n) if m!=j)+grid[i][j]
d=b.copy() #一定要copy啊啊啊啊
return min(d)
优化到O(1)的话,用小技巧。维护后m行最小值和次小值,以及最小值对应下标
python
class Solution:
def minFallingPathSum(self, grid: List[List[int]]) -> int:
for pre_row, cur_row in pairwise(grid): # 枚举上一行和当前行
mn, mn2 = nsmallest(2, pre_row) # 上一行的最小值和次小值
for j, pre in enumerate(pre_row): # 枚举上一行的状态
cur_row[j] += mn if pre != mn else mn2 # 不是最小就加上最小,否则加上次小
return min(grid[-1]) # 第 n-1 行的最小值
nsmallest 是 Python 中 heapq标准库模块的一个函数,用于找到一个可迭代对象中的前 n 个最小元素。它利用了堆数据结构的特性,可以在 O(n log k) 时间复杂度内完成操作,其中 n 是可迭代对象的元素数量,k 是要求的最小元素数量。heapq.nsmallest(n, iterable, key=None)