Python动态规划(DP)技巧套路详解及总结

Python 动态规划(DP)套路总结

在解决算法问题时,动态规划(DP) 是一种非常常见的优化技巧,它可以通过保存子问题的结果来避免重复计算,从而减少时间复杂度。Python 提供了非常方便的语法特性,使得实现动态规划变得相对简单。下面是一些常见的 Python 语法DP 解题套路

1. Python 语法和技巧

1.1 列表初始化

在 Python 中,动态规划通常需要一个数组来存储子问题的解,常见的初始化方法有:

  • 一维数组

    dp = [0] * n  # 初始化一个长度为 n 的数组,元素均为 0
    
  • 二维数组

    dp = [[0] * m for _ in range(n)]  # 初始化一个 n x m 的二维数组,元素均为 0
    
  • 处理边界情况
    如果问题涉及到无效值(例如负无穷),可以初始化为非常小的数字:

    dp = [-float('inf')] * n  # 初始化为负无穷
    
  • 1.2 枚举排列

  • 枚举排列:通常用于动态规划中,确定所有的选择路径:

    for i in range(n):
        for j in range(i, n):  # 遍历子数组
            # 在这里进行状态转移
    
  • 利用索引生成排序后的顺序

    sorted_idx = sorted(range(len(arr)), key=lambda i: arr[i])  # 排序并记录原始索引
    
  • 2. 动态规划(DP)解题套路

    2.1 状态定义

    首先,需要定义一个或多个状态,表示每个子问题的结果。常见的状态定义如下:

  • dp[i] 表示前 i 个元素的最大和或最小值
  • dp[i][j] 表示在第 i 个位置选择了 j 种操作后的最优解
  • 对于某些题目,可能需要额外记录其他信息,如最大或最小值、索引等。
  • 2.2 状态转移方程

    根据题目要求,写出从一个状态转移到另一个状态的递推公式。一般来说,转移方程会根据前一个状态的结果来更新当前状态。

    2.3 边界条件

    动态规划通常从小的子问题开始,需要明确边界条件:

  • dp[0] 可能是初始化的最小值。
  • dp[1] 可能是初始值或第一个元素的特殊处理。
  • 2.4 最终解的选择

    在动态规划的过程中,我们最终希望找到全局最优解(例如最大值或最小值)。通常,我们需要通过 max(dp)min(dp) 来获取结果。


    3. 题目解析与动态规划解法

    题目:1186. 删除一次得到子数组最大和

    题目描述

    给定一个整数数组 arr,返回它的某个非空子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),删除后该子数组中至少应当有一个元素。

    示例
    arr = [1,-2,0,3]
    输出: 4
    

    解释:

  • 我们可以选择子数组 [1, -2, 0, 3],删除 -2,得到 [1, 0, 3],最大和为 4
  • 解法思路

    对于这个问题,我们可以使用 动态规划(DP) 来处理。考虑两种情况:

    1. 不删除任何元素 的情况下,子数组的最大和。
    2. 删除一个元素后,子数组的最大和。

    1. 定义状态

  • dp[i][0]:表示到第 i 个元素为止,不删除任何元素时的最大和。
  • dp[i][1]:表示到第 i 个元素为止,删除一个元素后的最大和。
  • 2. 状态转移方程

  • dp[i][0]:可以选择加入当前元素,或者从当前元素开始一个新的子数组:
    dp[i][0] = max(arr[i], dp[i-1][0] + arr[i])
    
  • dp[i][1]:可以选择删除当前元素,或者删除前一个元素并加上当前元素:
    dp[i][1] = max(dp[i-1][0], dp[i-1][1] + arr[i])
    
  • 3. 边界条件

  • dp[0][0] = arr[0],表示子数组只有第一个元素的情况。
  • dp[0][1] = -inf,表示第一个元素不能删除,因为至少要保留一个元素。
  • 4. 最终解

    我们需要考虑两种情况的最大值:

  • dp[i][0] 表示不删除元素的最大和。
  • dp[i][1] 表示删除一个元素后的最大和。
  • 最终的解是 max(max(dp[i][0] for i in range(n)), max(dp[i][1] for i in range(n)))

    代码实现
    class Solution:
        def maximumSum(self, arr: List[int]) -> int:
            n = len(arr)
            
            # 初始化 dp 数组,初始值为负无穷
            dp = [[-2e9, -2e9] for _ in range(n)]
            
            # 初始化第一个元素的情况
            dp[0][0] = arr[0]
            dp[0][1] = float('-inf')  # 删除第一个元素不合法
            
            # 动态规划遍历
            for i in range(1, n):
                dp[i][0] = max(arr[i], dp[i - 1][0] + arr[i])  # 不删除
                dp[i][1] = max(dp[i - 1][0], dp[i - 1][1] + arr[i])  # 删除一个元素
            
            # 返回最大结果
            return max(max(dp[i][0] for i in range(n)), max(dp[i][1] for i in range(n)))
    
    解析:
    1. 初始化:我们初始化了 dp 数组,并为第一个元素设定了边界条件。
    2. 状态转移:我们通过遍历 arr 数组,逐步计算 dp[i][0]dp[i][1],表示不删除元素和删除元素后的最大和。
    3. 返回结果:最后返回 dp 数组中最大的值,即为子数组的最大和。

    时间复杂度与空间复杂度:

  • 时间复杂度O(n),我们只遍历了一次数组,并进行常数时间的计算。
  • 空间复杂度O(n),需要一个大小为 n 的动态规划数组。

  • 总结

    动态规划(DP)是解决子问题最优解的强大工具。在此题中,我们利用了动态规划的状态定义转移方程来解决删除一次元素后的子数组最大和问题。在处理此类问题时,理解状态定义与转移方程至关重要,它能够帮助我们高效地得出最终的最优解。
    两种状态 没有行使权力的最大数组和 行使权力后的最大数组和
    没有行使权力可以另起炉灶一个新的或者继承之前的
    行使权力的可以继承之前的,或者行使权力不删集成没有行使权力的

    作者:迪小莫学AI

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python动态规划(DP)技巧套路详解及总结

    发表回复