0
点赞
收藏
分享

微信扫一扫

算法之动态规划(一)

君之言之 2022-04-14 阅读 65

动态规划(Dynamic programming)

动态规划是算法中经常用到的,通过将原问题分解成相对简单的子问题来求解复杂的问题的方法。动态规划与分治方法相似,通过组合子问题的解来求解原问题。与之不同的是,动态规划应用在子问题有重叠的情况下,即不同的子问题有共同的子子问题。解决重复子问题,并将子问题的结果保存在表格内,这就避免重复计算。

动态规划一般求解最优化问题,因为有很多可行性解,每个解都有一个值,我们需要寻找最优解的值,这个值称为这个问题的一个最优解,而不是最优解,因为可以有很多解都能达到最优值。

所以动态规划能解决的问题是能将原问题分解成小问题,并且小问题需要重复计算的情况,以空间换时间,用dp类似的表格记录小问题的计算解,从而计算最终结果的解。类似递推加上记忆化加上剪枝。

题目实例

1. 爬楼梯

Leetcode 70

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

问题分析:

根据数学思维,假如现在在n台阶,那上一步是在n-1或者n-2台阶上,所以很容易得出f(n)=f(n-1)+f(n-2),边界是f(1)=1 ,f(0)=0

因此代码可以写成

def climbStairs(self, n: int) -> int:
    if n <= 3:
        return n
    return self.climbStairs(n - 1) + self.climbStairs(n - 2)
						                fib(5)   
                     /                \
               fib(4)                fib(3)   
             /        \              /       \ 
         fib(3)      fib(2)         fib(2)   fib(1)
        /    \       /    \        /      \
  fib(2)   fib(1)  fib(1) fib(0) fib(1) fib(0)
  /     \
fib(1) fib(0)

这时候采用简单的递归是能解决问题。但是在递归调用的时候有很多重复计算的子问题,所以这时候我们加上记忆化的操作就有下面的这层代码。

def climbStairs(self, n: int) -> int:
  use = {0: 1, 1: 1}
  def dp(n):
    if n in use:
      return use[n]
    use[n] = dp(n - 1) + dp(n - 2)
    return use[n]
  return dp(n)

这次通过将字典的方法将已经计算的结果保存进去,从而避免重复计算,因为这题比较简单所以不涉及剪枝的概念,所以动态规划的解法为:

def climbStairs(self, n: int) -> int:
  if n<=2:
    return 2
  dp=[0]*n
  dp[0],dp[1]=12
  for i in range(2,n):
    dp[i]=dp[i-1]+dp[i-2]
  return dp[-1]

由上可以理解动态规划可以类似动态递推。而dp的解法空间复杂度为O(n),若想采用O(1)的方法可以直接用递推recursion的方式,自底向上求解。

def climbStairs(self, n: int) -> int:
  if n<=3:
    retrun n
  f1,f2=12
  for _ in range(2,n):
    f1,f2,=f2,f1+f2
  return f2

当然这个方法也不是最优解,因为时间复杂度为O(n),若想加快的话,需要借助矩阵幂与分治的方法。可以降到O(logn)

2. 最大子序和

Leetcode 53

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

思路:

  1. 一般可以采用递归+记忆化也就是递推来考虑,
  2. 若是采用dp的化:
    1. 定义状态:dp[n]
    2. 状态转移方程:dp[n]=best(dp[n-1],dp[n-2]…) 此处为理解的难点。
    3. 最优子结构。

问题分析

  1. 这个题目可以定义dp[i] 为nums[i]结尾的时候连续最大子数组和,子问题为nums[i] 跟nums[i-1]相加是否更大
  2. 状态转移方程为,看dp[i] =max(nums[i],dp[i-1]+nums[i]),如果nums[i] 为正数,则dp[i-1]+nums[i] 更大,如果nums[i] 为负数,则需要更新dp[i]。此处的max类似剪枝的功能,这是一个比较简单的。dp问题的难点是状态转移中的类似剪枝的功能的寻找。
  3. 求出dp里面最大值。
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp=[0] * len(nums)
        dp[0]=nums[0]
        for i in range(1,len(nums)):
            dp[i]=max(nums[i],dp[i-1]+nums[i])  # 都为负数,取nums[i]
        return max(dp)

单独拿出状态转移方程的话可以考虑以下做法

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
      n=len(nums)
      max_value=nums[0]
      for i in range(1,n):
        if nums[i-1]>0:
          nums[i] +=num[i-1]
        max_value=max(nums[i],max_value)
      return max_value

此方法节省空间,只用原数组进行操作代替dp。

举报

相关推荐

0 条评论