算法基础——动态规划(2)网格图DP(基础)

算法基础------动态规划(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)

相关推荐
代码雕刻家27 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain27 分钟前
算法 | 位运算(哈希思想)
算法
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
我是哈哈hh4 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
Tisfy4 小时前
LeetCode 2187.完成旅途的最少时间:二分查找
算法·leetcode·二分查找·题解·二分
Mephisto.java4 小时前
【力扣 | SQL题 | 每日四题】力扣2082, 2084, 2072, 2112, 180
sql·算法·leetcode
robin_suli4 小时前
滑动窗口->dd爱框框
算法
丶Darling.5 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5205 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法