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

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

LCR 166. 珠宝的最高价值

常规dp(或者有递推的回溯,贪心等方法)都可以通过分析递归调用树(搜索树)来分析时间复杂度。其中树的层数代表做选择的次数,本题中任意方法选择次数相等,均为m(行数)+n(列数)次,故有m+n层,空间(递归的栈空间)用到m+n。又每次选择的选项仅有两个,故为二叉树,从而我们能计算递归调用次数2^(m+n)。

dp优化方法一般是:

  • 时间:备忘录(记忆化搜索)-> 自底向上(递推)

对本题的优化方法:

  • 空间:两个数组(滚动数组)->一个数组 ->原地修改

代码如下
1、递推:
dfs函数改为d数组
递归边界改为数组初始值
递归改成循环

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)

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)

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)

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)

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可以避免使用中间变量。

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在判断中记录下一行可以移动到的元素。

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)

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)

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行最小值和次小值,以及最小值对应下标

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)

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TSP问题(Traveling Salesman Problem,旅行商问题)是一个经典的组合优化问题,它要求在给定的城市之间找到一条最短路径,使得每个城市只被经过一次,并且最终回到起点。 在本文中,我们将介绍如何使用Python解决TSP问题的动态规划算法动态规划算法 动态规划算法是一种解决复杂问题的有效方法,它通常用于优化问题。TSP问题的动态规划算法的思路是:将问题分解为子问题,然后通过计算子问题的最优解来逐步构建整个问题的最优解。 具体来说,我们可以使用以下步骤来解决TSP问题: 1. 定义状态:将TSP问题定义为一个二元组$(S,i)$,其中$S$表示已经经过的城市集合,$i$表示当前所在的城市。 2. 定义状态转移方程:我们定义$dp(S,i)$表示从城市$i$出发,经过集合$S$中所有城市的最短路径长度。状态转移方程为: $$ dp(S,i) = \begin{cases} 0 & \text{if } S=\{i\} \\ \min\limits_{j\in S,j\ne i}\{dp(S-\{i\},j)+dist[j][i]\} & \text{otherwise} \end{cases} $$ 其中$dist[i][j]$表示城市$i$到城市$j$之间的距离。 3. 初始状态:$dp(\{i\},i)=0$。 4. 最终状态:$dp(\{1,2,\cdots,n\},1)$即为所求的最短路径长度。 代码实现 下面是使用Python实现TSP问题动态规划算法的代码: ```python import math def tsp_dp(dist): n = len(dist) # 记录子问题的最优解 dp = [[math.inf] * n for _ in range(1 << n)] # 初始状态 for i in range(n): dp[1 << i][i] = 0 # 构建状态转移方程 for s in range(1, 1 << n): for i in range(n): if s & (1 << i) == 0: continue for j in range(n): if i == j or s & (1 << j) == 0: continue dp[s][i] = min(dp[s][i], dp[s ^ (1 << i)][j] + dist[j][i]) # 返回最终状态 return min(dp[(1 << n) - 1][i] + dist[i][0] for i in range(n)) # 示例 dist = [ [0, 2, 9, 10], [1, 0, 6, 4], [15, 7, 0, 8], [6, 3, 12, 0] ] print(tsp_dp(dist)) # 输出:21 ``` 在上面的代码中,我们首先使用$dp$数组记录子问题的最优解,然后通过状态转移方程逐步构建整个问题的最优解。 最后,我们通过计算$dp(\{1,2,\cdots,n\},1)$和从最后一个城市回到起点的距离之和的最小值来得到TSP问题的最优解。 总结 通过本文,我们学习了如何使用Python解决TSP问题的动态规划算法。TSP问题是一个经典的组合优化问题,它的解决方法还有很多其他的算法,例如分支定界算法、遗传算法等。如果你对这些算法感兴趣,可以进一步学习相关的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值