[Leetcode 专题] 动态规划


动态规划常常适用于 有重叠子问题最优子结构性质的问题,动态规划方法所 耗时远小于朴素解法。

1.主要思想

若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。动态规划往往用于优化递归问题,例如斐波那契数列,如果运用递归的方式来求解会重复计算很多相同的子问题,利用动态规划的思想可以减少计算量。
动态规划法仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量,一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。

2.动态规划步骤

1.确定动态规划状态;
2.写出状态转移方程(画出状态转移表);
3.考虑初始化条件;
4.考虑输出状态;
5.考虑对时间,空间复杂度的优化(Bonus)。

3.应用举例:Leetcode 300.最长上升子序列

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例
输入:【10,9,2,5,3,7,101,18】
输出:4
解释:最长上升子序列是【2,3,7,101】,它的长度是4。

注:
1.可能会有多种最长上升子序列组合,你只需输出对应的长度即可。
2.算法的时间复杂度应该为O(n2)。

解题思路:

Step1 确定动态规划状态

1.是否存在状态转移?
2.什么样的状态比较好转移,找到对求解问题最方便的状态转移。
想清楚到底是直接用需要求的,比如长度作为dp保存的变量还是用某个判断问题的状态比如是否是回文子串来作为方便求解的状态。
该题目可以直接用一个一维数组dp来存储转移状态,dp[i] 可以定义为以 nums[i] 这个数结尾的最长递增子序列的长度。举个实际例子,比如在 nums[10,9,2,5,3,7,101,18] 中,dp[0] 表示数字10的最长递增子序长度,那就是本身,所以为1,对于dp[5] 对应的数字7来说的最长递增子序列为 [2,5,7] 或者 [2,3,7] ,所以 dp[5]=3

Step2 写出一个好的状态转移方程

1.使用数学归纳法思维,写出准确的状态方程
仍以刚才的 nums 数组为例,我们思考一下是如何得到dp[5]=3的:既然是递增的子序列,我们只要找到nums[5](也就是7)前面那些结尾比7小的子序列,然后把7接到最后,就可以形成一个新的递增的子序列,这个新的子序列就是在找到前面的那些数后面加上7,长度则加1。当然会找到很多不同的子序列,比如刚才上面说的,但是只需要找到长度最长的作为dp[5]的值就行。总结来说就是比较当前dp[i]的长度和dp[i]对应产生新的子序列的长度,我们用j来表示所有的比i小的组数中的索引,可以用如下的代码表示:

for i in range(len(nums)):
    for j in range(i):
        if nums[i]>nums[j]:
             dp[i]=max(dp[i],dp[j]+1)

Step3 考虑初始条件

这是决定整个程序能否跑通的重要步骤,当我们确定好状态转移方程,我们就需要考虑一下边界值,边界值考虑又分为三个部分:
1.dp数组整体的初始值
2.dp数组(二维)i=0和j=0的地方
3.dp存放状态的长度,是整个数组的长度还是数组长度加一

Step4 考虑输出状态

主要有以下三种形式,对于具体问题,我们一定要想清楚到底dp数组里存储的是哪些值:
1.返回dp数组中最后一个值作为输出,一般对应二维dp问题。
2.返回dp数组中最大的那个数字,一般记录最大值问题。
3.返回保存的最大值,一般是 Maxval=max(Maxval,dp[i]) 这样的形式。

def lengthOfLIS(self, nums: List[int]) -> int: 
    if not nums:return 0 #判断边界条件 
    dp=[1]*len(nums) #初始化dp数组状态
    for i in range(len(nums)):
      for j in range(i):
        if nums[i]>nums[j]: #根据题目所求得到状态转移方程
          dp[i]=max(dp[i],dp[j]+1) 
          return max(dp) #确定输出状态

Step5 考虑对时间、空间复杂度的优化(Bonus)

切入点:
我们看到,之前方法遍历dp列表需要 O(N) 的时间,计算每个dp[i]需要 O(N) 的时间,所以总复杂度为O(N2)

模版总结:

for i in range(len(nums)):
    for j in range(i):
        dp[i]=最值(dp[i],dp[j]+...)
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值