动态规划--一维dp和二维dp

在解决背包问题时,使用一维动态规划数组和二维动态规划数组都是常见的方法,选择哪种方式取决于问题的特点和解法的需要。

使用一维DP数组的情况:

  1. 状态转移方程只涉及到上一行的元素:

    • 当状态转移方程只涉及到上一行的元素时,可以使用一维DP数组。这样能够降低空间复杂度,使算法更为简洁。
  2. 问题中只需要考虑当前状态的前一个状态:

    • 如果问题中只需要考虑当前状态和前一个状态之间的关系,而不需要考虑更远的状态,可以选择使用一维DP数组。

使用二维DP数组的情况:

  1. 状态转移方程涉及到上一行和当前行的元素:

    • 当状态转移方程涉及到上一行和当前行的元素时,通常需要使用二维DP数组。例如,背包问题中的状态转移方程常常涉及到两个维度,一个表示物品,一个表示背包容量。
  2. 问题需要保留更多的信息:

    • 如果问题需要保留更多的信息,可能需要使用二维DP数组来存储这些额外的信息,以便后续计算。例如,记录达到当前状态的路径、组合方式等。
  3. 更直观的表示状态:

    • 对于一些复杂的问题,使用二维DP数组可以更直观地表示问题的状态和状态之间的关系,提高代码的可读性。

示例:

一维DP数组示例:
dp = [0] * (target+1)
for num in nums:
    for i in range(target, num-1, -1): #0-1背包一维常倒序(倒序为了避免重复),完全背包常正序
        dp[i] += dp[i-num]
二维DP数组示例:
dp = [[0] * (target+1) for _ in range(len(nums)+1)]
for i in range(1, len(nums)+1):
    for j in range(target+1):   #二维常正序 不同行(dp[i][..]和dp[i-1][..])不会重复
        dp[i][j] = dp[i-1][j]
        if j >= nums[i-1]:
            dp[i][j] += dp[i-1][j-nums[i-1]]

总的来说,选择使用一维DP数组还是二维DP数组,取决于问题的特点和解法的需要。在一些情况下,通过巧妙的设计,可以将二维DP数组优化成一维DP数组。

上一行和当前行的含义
在动态规划中,经常会用到“上一行”和“当前行”这两个概念,尤其是在使用二维动态规划数组时。这两者的区别在于它们对应于不同的状态或阶段。

  1. 上一行(Previous Row):

    • 指的是当前阶段之前的一个阶段,也就是在DP数组中的上一行。
    • 在二维DP数组中,通常表示为dp[i-1][...],其中i是当前行的索引。
    • 上一行对应于之前的状态,用来计算当前行的状态。
  2. 当前行(Current Row):

    • 指的是当前阶段,也就是在DP数组中的当前行。
    • 在二维DP数组中,通常表示为dp[i][...],其中i是当前行的索引。
    • 当前行对应于当前状态,是根据上一行的状态计算得到的。

在讨论背包问题时,这两者的具体含义可以理解为:

  • 上一行: 表示考虑到当前物品之前的状态,即在选择当前物品之前的状态。
  • 当前行: 表示在考虑当前物品时的状态,即在选择当前物品后的状态。

**如果 每个状态与不仅与上一行有关 改用一维dp时 要用临时变量等来防止覆盖 **
eg:https://leetcode.cn/problems/is-subsequence/
https://programmercarl.com/1143.%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC
例题:最长公共子序列:

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        #二维dp  教程:https://programmercarl.com/1143.%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC
        m = len(text1)
        n = len(text2)
        ## 创建一个二维数组 dp,用于存储最长公共子序列的长度
        #dp = [[0]*(m+1) for _ in range(n+1)] 应用(m+1)x(n+1)纬度
        #m 和 n 都加一 表示考虑空串的情况
        dp = [[0]*(n+1) for _ in range(m+1)]
        #初始长度为0,不必再赋值
        # 将 dp[0][j] 和 dp[i][0] 设置为 0,表示空串的情况。然后,我们从 i=1 和 j=1 开始遍历,逐步填充 dp 数组。

        # 如果我们从 i=0 和 j=0 开始遍历,那么 dp[0][j] 和 dp[i][0] 将一直保持为 0,因为它们表示空串的情况。这样,我们就无法正确计算最长公共子序列的长度,因为我们无法利用之前的计算结果。

        # 通过从 i=1 和 j=1 开始遍历,我们可以确保 dp[i][j] 的计算能够利用之前已经计算过的值,因为 dp[i-1][j]、dp[i][j-1] 和 dp[i-1][j-1] 的值已经在之前的迭代中被计算过了。这种逐步填充的方式保证了动态规划的正确性。
        for i in range(1,m+1):
            for j in range(1,n+1):
                #每个状态和左、上、左上有关
                if text1[i-1] == text2[j-1]:
                    dp[i][j] = dp[i-1][j-1]+1
                else:
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1])
        return dp[m][n]
       

具体到代码中,通过这两者的概念,我们可以方便地设计状态转移方程,使用前一行的信息来更新当前行的信息,从而实现动态规划的递推过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值