LeetCode分类训练 Task 2动态规划

群内编号:2-木铎铎
博文内容学习自DataWhale开源文档:
LeetCode分类训练 Task 2 动态规划


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

主要思想

要解一个给定问题,需解其不同子问题,再根据子问题的解得出原问题的解。动态规划(DP)往往用于优化递归问题,如斐波那契数列,运用递归的方式求解会重复计算很多相同子问题,利用DP思想可减少计算量。

DP仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。一旦某个给定子问题的解已经算出,则将其记忆化存储,下次需要同一个子问题的解时查表即可

动态规划模板步骤:

  • 确定动态规划状态

  • 写出状态转移方程(画出状态转移表)

  • 考虑初始化条件

  • 考虑输出状态

  • 考虑对时间,空间复杂度的优化(Bonus)

经典案例 1:Leetcode 300.最长上升子序列

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

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4

说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n^2)
第一步:确定动态规划状态

该题目可以直接用一个一维数组dp来存储转移状态,dp[i]可以定义为以nums[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)

Tips:在实际问题中,如果不能很快得出这个递推公式,可以先尝试一步一步把前面几步写出来,如果还是不行很可能就是 dp 数组的定义不够恰当,需要回到第一步重新定义 dp 数组的含义;或者可能是 dp 数组存储的信息还不够,不足以推出下一步的答案,需要把 dp 数组扩大成二维数组甚至三维数组。

第三步:考虑初始化条件

这是决定整个程序能否跑通的重要步骤,当我们确定好状态转移方程,我们就需要考虑一下边界值,边界值考虑主要又分为三个方面

  • dp数组整体的初始值

  • dp数组(二维)i=0和j=0的地方

  • dp存放状态的长度,是整个数组的长度还是数组长度加一,这点需要特别注意。

对于本问题,子序列最少也是自己,所以长度为1,这样我们就可以方便的把所有的dp初始化为1,再考虑长度问题,由于dp[i]代表的是nums[i]​的最长子序列长度,所以并不需要加一。 所以用代码表示就是​dp=[1]*len(nums)

第四步:考虑输出状态

主要有以下三种形式,对于具体问题,我们一定要想清楚到底dp数组里存储的是哪些值,最后我们需要的是数组中的哪些值:

  • 返回dp数组中最后一个值作为输出,一般对应二维dp问题。

  • 返回dp数组中的最大值,一般对应记录最大值问题。

  • 返回保存的最大值,一般是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)  #确定输出状态
第五步:考虑对时间,空间复杂度的优化(Bonus)

请参见:
二分方法+动态规划详解

经典案例 1:Leetcode5. 最长回文子串

题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
第一步:确定动态规划状态

这里定义dp[i][j]表示子串s从i到j是否为回文子串。

第二步:写出状态转移方程
# 字符串首尾两个字符必须相等,否则肯定不是回文
if s[i]==s[j]:
	# j-1-(i+1)+1<2
	if j-i<3:
		dp[i][j]=True
	else:
		dp[i][j]=dp[i+1][j-1]
第三步:考虑初始化条件

由于只有一个字符的时候肯定是回文串,所以dp表格的对角线dp[i][i]肯定是True。

第四步:考虑输出状态

这里dp表示的是从i到j是否是回文子串,这样一来就告诉我们子串的起始位置和结束位置。

if dp[i][j]: 
# 只要dp[i][j]成立就表示是回文子串,之后记录位置,返回有效答案
    cur_len=j-i+1
    if cur_len>max_len:
    	max_len=cur_len
    	start=i
第五步:考虑对时间,空间复杂度的优化

请参考下面的题解:
动态规划、Manacher 算法

结语

动态规划除了解决子序列问题,也可以用来解决其他实际的问题,比如一些AI的经典算法。本文只简单记录了一些基础的用法和2道经典的题目,更详细的分析和更多的例子请见原文(地址在本文顶部)。日后对DP算法还要多多应用,多多理解^ _ ^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值