(标题就叫这个吧,我也没什么主意了)
动态规划,要给这个这个东西下个定义,确实不太好下,他是一种基于状态来思考问题的算法思想
用来表示状态的话,那就是dp,(这么说好抽象),就直接说涉及动态规划的题目怎么处理吧
,这个还是有步骤可行的,就按如下步骤操作
1.寻找子问题
2.找出状态转移方程
3.最后思考这个是不是最优子结构,即子问题是否可以推出原问题的最优解
4.当然,还有就是确定边界条件,这个东西可以理解为一个类似递归出口的东西(大多数情况下,用递归会使时间极其复杂,所以一般采用循环)
同时,在思考动态规划时要记得无后效性,这个意思就是说只考虑当前状态,而不用考虑过去和未来(这个作者在说什么,也挺抽象的)
确实,这么说概念没有用,就先来看几个基本的例子和代码吧
首先是第一题,走楼梯,这题太典了(Leetcode 70)
在思考这道题的时候就按照上面的步骤来走首先寻找子问题
这道题的子问题就是对于第n个台阶,从n-1个台阶走到n有几种方案,从n-2个台阶走到n有几种方案,因为题目说了只可以有1个台阶或2个台阶;
既然知道了方案的子问题,那么就要去写状态转移方程
我们假设dp表示到i的方案数
那么dp[i]=dp[i-1]+dp[i-2];
那么基于这个状态转移方程,就可以去求方案数了,
那么见下面代码
class Solution:
def climbStairs(self, n: int) -> int:
dp=[0]*(46)
dp[0]=1
dp[1]=1
dp[2]=2
for i in range(3,46):
dp[i]=dp[i-1]+dp[i-2]
# print(dp)
# print(len(dp))
return dp[n]
前面先设置dp的边界条件,因为询问是从1开始,范围是45,那么就将dp的值设为46,保证足够长,接着到1上去有1种方案,到2有2种方案,如此,便可以去表示出到n的dp数组,因为这里数据最大只到45,那么就直接枚举就可以了。
当然,这道题也可以使用递归,这也是一种方案,只是和动态规划没有什么关系
class Solution:
def climbStairs(self, n: int) -> int:
memo = {}
def helper(n):
if n == 1:
return 1
if n == 2:
return 2
if n not in memo:
memo[n] = helper(n-1) + helper(n-2)
return memo[n]
return helper(n)
那么下一道题是这个题的变种,来自蓝桥云课
蓝桥云课3367
其实这道题相比而言多的步骤就是由于破损的楼梯不可以踩,所以这个题里面就要先去对dp数组中不可以踩的部分进行标记,也就是使用一个vis数组来标记,这里先去遍历一遍坏的楼梯,将他们给记录下来,之后再来进行和前面一样的操作,就是在损坏的地方方案数记录为0就可以了,最后打印出结果
n,k=map(int,input().split())
a=list(map(int,input().split()))
mod=1000000007
dp=[0]*100010
vis=[0]*100010
for i in a:
vis[i]=1
#print(vis)
dp[0]=1
dp[1]=1-vis[1]
for i in range(2,n+1):
if vis[i]:
continue
else:
dp[i]=int((dp[i-1]+dp[i-2])%mod)
print(dp[n])
当然,对于动态规划的dp,也不止这么一点东西,那么,难得要来了,就是二维dp
二维dp和一维dp其实本质上是差不多的,但是不同的地方在于二维dp需要用两个变量来表示当前状态,他的状态转移方程也是由两个一个二维dp数组来处理的,感觉这么说挺抽象的,就来看下面的例题
Leetcode 120 三角形的最小路径和
那么,这道题和动态规划有什么关系呢?
那还是得从状态转移方程来看,状态转移方程其实我们可以表示为到trangle[i][j]为止的和,那么这
dp[i][j]是怎么来的呢,就是dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+a[i][j],这样子,就可以求出来最小的dp值,那么边界条件是什么呢,边界条件就是当他到最后一行时,dp数组只能接受最后一行的数字,那么这样子就可以去对dp数组做处理了
下面是代码
class Solution:
def minimumTotal(self, triangle: list[list[int]]):
n=len(triangle)
dp=[[0]*(n+1) for i in range(n+1)]
new_triangle=[[0]*(n+1) for i in range(n+1)]
for i in range(len(triangle)):
for j in range(len(triangle[i])):
new_triangle[i+1][j+1]=triangle[i][j]
#扩大矩形的范围
m=len(dp)
for i in range(m-1,-1,-1):
for j in range(i+1):
if i==m-1:
dp[i][j]=new_triangle[i][j]
else:
dp[i][j]=min(dp[i+1][j+1],dp[i+1][j])+new_triangle[i][j]
return dp[1][1]
在这里,需要注意的一点是对三角形做处理,为了不让三角形超出预期的范围,就要对数组进行处理,使其扩大数据范围,防止报错
下面是比较有难度的题,
蓝桥云课(我也不知道是几号)
那么这个题首先也是先去求他的状态转移方程,那么对于第n条街,若是修建了1间房子,说明前
n-1条街修建k-1间房子,若修了2间房子,则前n-1条街就修了k-2间房子。。。。。以此类推,当n-1条街修了m间房子,前n-1条街就修了k-m间房子,这样子下来,则可以求出来求出他的递推公式
dp[n][k]=dp[n-1][k-1]+dp[n-1][k-2]+.....+dp[n-1][k-m] 而其边界条件则为dp[0][0]=1表示第0条街修0间只有1种方案(就是什么都不做),最后,就可以写出来dp数组,如此,就可以求出来第n条街的情况。
注意对数据取模,防止过大,代码如下
#2 3 5测试用例-->8
#3 3 5测试用例--->10
n,m,k=map(int,input().split())
mod=1000000007
#第n条街,修1间房->n-1条街,修k-1间房
#第n条街,修2间房->n-1条街,修k-2间房
#.....
#第n条街,修m间房->n-1条街,修k-m间房
#到第n条街为止,一共修了k间房
#dp[n][k]=dp[n-1][k-1]+dp[n-1][k-2]+.....+dp[n-1][k-m]
dp=[[0]*(k+1) for _ in range(n+1)]
dp[0][0]=1#(只有1条街,修了一间房子)
for i in range(1,n+1):#枚举第n条街道
for j in range(i,k+1):#枚举一共有k
for s in range(1,min(j-i+1,m)+1):
dp[i][j]+=dp[i-1][j-s]
dp[i][j]%=mod
print(int(sum(dp[n])%mod))
那么就到这里了,先停了。