基础动态规划讲解

(标题就叫这个吧,我也没什么主意了)

动态规划,要给这个这个东西下个定义,确实不太好下,他是一种基于状态来思考问题的算法思想

用来表示状态的话,那就是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))

那么就到这里了,先停了。

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值