动态规划的练习总结(Python)

前言:不适合无任何基础的小白,需要先看视频搞懂原理,这是自己的刷题总结

这几道题是看这位博主告别动态规划的讲解,写了python版本的答案(目前不考虑代码结构优化,只是做出题)

1、青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

def maxF(n):
    #创建长度为n+3的列表,避免n=0时,dp数组超限
    dp=[None]*(n+3)
    if n<0:
        print("台阶个数应大于等于0个,请核查输入%d"%n)
    else:
        # 初始条件
        dp[0] = 0
        dp[1] = 1
        dp[2] = 2
    if n>0 and n<3:
        print(dp[n])
    else:
        i=3
        while i<=n:
            #重点是找到这个关系,直接思路的运算并没有做优化
            dp[i] = dp[i - 1] + dp[i - 2]
            i+=1
        print(dp[n])


num=input()
maxF(int(num))

2、网格移动路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

def maxF(m,n):
    #创建的二维列表,range(1)输出0,range(2)输出0,1,range不是n+1是因为m,n=0直接归为0
    dp = [[0] * n for _ in range(m)]
    if m<=1 and n<=1:
        print(0)
    else:
        # 初始条件
        #range从0开始,这里dp[0][0]=1,没关系不会用到
        for i in range(m):
            dp[i][0] = 1
        for j in range(n):
            dp[0][j] = 1

        i = 2
        if m==1 or n==1:
            print("1")
        else:
            while i <= m:
                j = 2
                while j <= n:
                    dp[i-1][j-1] = dp[i - 2][j-1] + dp[i-1][j - 2]
                    j += 1
                i += 1
            #这有很强的误导性,输出dp[i-2][j-2]并不是因为输出的是dp[i-1][j-1]上一个,而是因为要跳出循环,i和j都再加了1
            #所以这里dp[i-2][j-2]是表示上面算出的dp[i-1][j-1]
            print(dp[i - 2][j - 2])

str=input()
m=str.split()[0]
n=str.split()[1]
maxF(int(m),int(n))

3、网格移动最优路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

import sys

def minF(arr,m,n):

    dp = [[0] * n for _ in range(m)]

    if m<=0 and n<=0:
        print(0)
    else:
        # 初始条件
        dp[0][0]=arr[0][0]
        for i in range(m):
            if i>=1:
                dp[i][0] = dp[i - 1][0] + arr[i][0]

        for j in range(n):
            if j>=1:
                dp[0][j] = dp[0][j - 1] + arr[0][j]
        i = 2
        while i <= m:
            j = 2
            while j <= n:
                dp[i-1][j-1] =min(dp[i - 2][j-1] ,dp[i-1][j - 2])+ arr[i-1][j-1]
                j += 1
            i += 1
        print(dp[i - 2][j - 2])

#读取输入的数据
#readlines在输入数据时,才能ctrl+d停止输入
#在编译输入时,pause the process to use command错误要先打开显示调试控制台,再ctrl+d
#下次用readline()
str=sys.stdin.readlines()
q=[]
arr=[]
i=1
while i<len(str)-1:
    q_input=[]
    m = str[i].split("]")[0]
    n=m.split("[")[1]
    q=n.split(",")
    for j in q:
        q_input.append(int(j))
    arr.append(q_input)
    i+=1
#三个参数:二维数组,行数,列数
#不会命名,乐扣不识别...能运行就好
minF(arr,i-1,len(q_input))

4、编辑距离

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作:插入,删除,替换

def minD(word1, word2):
    # m,n是单词的长度
    m = len(word1)
    n = len(word2)
    # dp[i][j]是word1长度为i,word2长度为j时,word1转换成word2使用的操作数
    #m,n都+1是因为还要包括word1和word2字符为0的初始情况
    dp = [[0] * (n+1) for _ in range((m+1))]
    # 两者相等,则不需要调整
    if word1 == word2:
        print(0)
    else:
        # 初始条件
        dp[0][0]=0
        # 删除
        #因为当有一个字符串的长度为 0 时,转化为另外一个字符串,无论删还是增,都是+1
        #删:指word2字符为0,word1删,增:指word1字符为0,word1增
        for i in range(m+1):
            if i > 0:
                dp[i][0] = dp[i - 1][0] + 1
        # 插入
        for j in range(n+1):
            if j > 0:
                dp[0][j] = dp[0][j - 1] + 1

        i = 1
        while i < m+1:
            j = 1
            while j < n+1:
                #word-1,dp不-1,是因为word[0]代表已经是一个字符,而dp[0]代表0个字符
                if word1[i-1] == word2[j-1]:
                    dp[i][j] = dp[i - 1][j - 1]
                else:
                    dp[i][j] = min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1
                j += 1
            i += 1
            # 这里的dp[i-1][j-1]就是代表上面计算出的dp[i][j],因为i,j跳出循环多+1了
        print(dp[i - 1][j - 1])


str = input()
word1 = str.split()[0]
word2 = str.split()[1]
minD(word1, word2)

这道题比较抽象,实际运算如下图进行:

例1:bay转换到boy

列为word1,行为word2

可以大致看作第一行是增,第一列是删,橘色的单元格是三种操作交叉使用。所以,不管有多少行、列,第一行第一列是与橘色部分分开运算,就是在0x0的单元格的值的基础上累加

1x1这个单元格,先判断b➡b是否相等,若相等,此单元格的操作次数=min(0x0,0x1,1x0)

若不相等,此单元格的操作次数=min(0x0,0x1,1x0)+1

其他橘色单元格都是相同的计算方式

3x3:y➡y,相等,操作次数=min(2x2,2x3,3x2)

0x0

0

0x1

""➡b

1

0x2

""➡bo

2

0x3

""➡boy

3

1x0

b➡""

1

1x1

b➡b

转换操作:0

1x2

b➡bo

转换操作:1

1x3

b➡boy

转换操作:2

2x0

ba➡""

2

2x1

ba➡b

转换操作:1

2x2

ba➡bo

转换操作:1

2x3

ba➡boy

转换操作:2

3x0

bay➡""

3

3x1

bay➡b

转换操作:2

3x2

bay➡bo

转换操作:2

3x3

bay➡boy

转换操作:1

这道题来自这位博主动态规划入门篇,只写到从下往上计算,还是使用二维数组这一步

5、数字三角形

在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99

import sys
import copy
def maxF(n, arr):
    #对dp进行初始化,比直接建立nxn的数组更节省空间
    dp=copy.deepcopy(arr)
    for i in range(len(arr)):
        for j in range(i+1):
            dp[i][j]=0
    #数组的下标从0开始
    i = n-1
    while i >= 0:
        j = i
        while j >=0:
            #最后一行不变
            if i==n-1:
                dp[i][j] = arr[i][j]
            else:
                dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + arr[i][j]
            j -= 1
        i -= 1
        # 这里的dp[i-1][j-1]就是代表上面计算出的dp[i][j],因为i,j跳出循环多+1了
    print(dp[0][0])

str=sys.stdin.readlines()
a=[]
for line in str:
    a.append(line)
n =int(a[0])
j = []
arr=[0]*n
for i in range(1,n+1):
    str[i-1]=a[i]
    str_num=[]
    #1,3,5,7,9...这样的顺序,i从1开始
    j.append(i-1)
    for k in j:
        str_num.append(int(str[i-1].split()[k]))
    arr[i-1]=(str_num)

maxF(n, arr)

6、跳跃游戏

给出一个非负整数数组,你最初定位在数组的第一个位置。数组中的每个元素代表你在那个位置可以的跳跃最大长度。判断你是否能到达数组的最后一个位置。

#问题一的解法一
#定义的dp数组是一个bool数组,dp[i]表示能否到达索引为i的位置
#从前往后,遍历输入数组里的每一个数,更新的可移动的最大步数,与目前的步数进行比较

'''
def SF(Arr):
    #初始化dp数组
    n=len(Arr)
    dp=[False]*n
    maxn=0
    for i in range(len(Arr)):
       if i>maxn:
           return dp[i]
       maxn=max(maxn,Arr[i]+i)
       dp[i]=True
    return dp[i]
'''
#问题一的解法二
#从后往前,从倒数第二个往前
#遍历数组的这个值与最小步长至少为1比较,如果值等于0,则最小步长+1;如果值大于步长,则再从1开始
def SF(Arr):
    n=len(Arr)
    dp=[False]*n
    Arr_re=Arr[::-1]
    Arr_re2=Arr_re[1:]
    j=1
    k=0
    for i in Arr_re2:
        if i>=j:
            dp[k]=True
            j=1
        else:
            j+=1
        k+=1
    return dp[k-1]

str=input()
str=str.split('[')[1]
str=str.split(']')[0]
Arr_str=(str.split(','))
Arr=[]
for i in Arr_str:
    Arr.append(int(i))
print(SF(Arr))

这个题本身不难,但是看了一些编码方法,硬套动态规划公式dp[i]、dp[i-1]把这题复杂化了。

这里用到的两个方法都来自b站这个博主55题跳跃游戏的视频讲解

7、解码方法

简单版:

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

'A' -> 1
'B' -> 2
...
'Z' -> 26

要解码已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)

def DeCoding1(arr):
    n=len(arr)
    #初始化dp
    dp=[0]*(n+1)
    dp[0]=1
    #这两步可以简写为dp=[1]+[0]*n
    for i in range(1,n+1):
        #使用一个字符,只要字符不等于0,就可以把1-9解码为A-I,所以dp[i]=dp[i-1],等于1个解码方式
        if arr[i-1]!=0:
            #此时dp[i]因为初始化使用时一直等于0,所以加上本身相当于加上0,也是dp[i]=dp[i-1]的含义
            dp[i]+=dp[i-1]
        #使用两个字符,只要第一个字符不等于0,就可以把两个字符的范围限制在10-26,所以dp[i]=dp[i-2],等于1个解码方式
        #dp[i]+=dp[i-2]这里是累加的意思,如果这个字符单个可以解码并且和前一个字符组合也可以解码,则单个字符dp[i]为1,两个字符dp[i]更新为2
        if i>1 and arr[i-2]!=0:
            limit=arr[i-2]*10+arr[i-1]
            if limit>=10 and limit<=26:
                #此时
                dp[i]+=dp[i-2]
    return dp[n]


#输入数字解码为字母
num=input()
arr=[]
for i in num:
    arr.append(int(i))
print(DeCoding1(arr))

这题参考了力扣官方题解11来了的思路。

分类讨论,dp[i]=dp[i-1]和dp[i]=dp[i-2]的情况,最后输出是这两种情况的累加

进阶版:除了上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符,可以表示从 '1' 到 '9' 的任一数字(不包括 '0'

def DeCoding2(arr):

    def check1(a):
        if a=="*":
            return 9
        elif a=="0":
            return 0
        else:
            return 1

    def check2(b1,b2):
        if b1==b2== "*":
            return 15
        elif b1 == "*":
            if b2<="6":
                return 2
            #7-9
            else:
                return 1
        elif b2 == "*":
            if b1 == "1":
                return 9
            elif b1 == "2":
                return 6
            else:
                return 0
        else:
            if b1!="0":
                a=int(b1)*10+int(b2)
                if a>=10 and a<=26:
                    return 1
                else:
                    return 0
            else:
                return 0
    # 10**9是10的9次方
    mod = 10 ** 9 + 7
    n=len(arr)
    #初始化dp[i]
    dp=[1]+[0]*n
    for i in range(1,n+1):
        dp[i]=dp[i-1]*check1(arr[i-1])
        if i>1:
            #两个字符的情况是dp[i]==dp[i-2]*check2(arr[i-2],arr[i-1])
            #累加是计算最后的结果
            dp[i]+=dp[i-2]*check2(arr[i-2],arr[i-1])
        #dp[i]值太大,除以mod取余
        dp[i]%=mod
    return dp[n]


#输入数字解码为字母
num=input()
arr=[]
for i in num:
    arr.append(i)
print(DeCoding2(arr))

这个进阶版在理解简单版的基础上,就很容易看懂了。唯一的区别就是将dp[i]=dp[i-1]+dp[i-2]中的dp[i-1]和dp[i-2]的情况细分有*、无*讨论,再像简单版一样分析0的存在;两个字符的再需要考虑,是一个字符有*,还是两个字符有*

8、三色旗

给定一个包含蓝色、白色和红色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照蓝色、白色、红色顺序排列。 #此题中,我们使用整数 0、 1 和 2 分别表示蓝色、白色和红色。

def swap(i,j):
    #两者交换
    a=arr[i]
    arr[i]=arr[j]
    arr[j]=a

def colorSort(arr):
    start=cur=0
    end=len(arr)-1
    while cur<=end:
        if arr[cur]==0:
            swap(start,cur)
            start+=1
            cur+=1
        elif arr[cur]==1:
            cur+=1
        elif arr[cur]==2:
            swap(end,cur)
            end-=1

str1=input()
str2=str1.split("[")[1].split("]")[0].split(",")
arr=[]
for i in str2:
    arr.append(int(i))
colorSort(arr)
print(arr)

这题的解题的思想值得学习。详见这位博主Oll-chen,思路清晰明了

9、分割回文串

给定字符串 s, 需要将它分割成一些子串, 使得每个子串都是回文串. 最少需要分割几次?

这个题有好几种写法,但思路是一样的,参考这位博主WuKongGo写了很详细的说明

def minCut(arr):
    n=len(arr)
    #表示从i到最后一个字符的最小切割数
    #dp需要n+1,是因为dp[i]是倒推,最后一个字符n需要与n+1的这个数进行比较
    dp=[0]*(n+1)
    #判断字符串从i到j是否为回文串
    p=[[False]*n for i in range(n)]
    #无字符或者字符个数为1,都无法分割
    if n<=1:
        return 0
    #为了保证最后一个字符n进行比较的dp[n]+1等于0
    dp[n]=-1
    #i从最后一个字符开始倒推
    i=n-1
    while i>=0:
        #dp[i]初始值设为最大int值,为了第一次对dp[i]赋值为dp[j+1]+1
        #后几次的赋值就根据公式比较大小
        dp[i]=2147483647
        #j的取值范围为range(i,n),是因为p[i][j]表示从第i个字符到第j个字符是否为回文串,所以i必须小于j,其他的值无意义
        for j in range(i,n):
            #判断回文串的条件:一、第i个字符与第j个字符相等;
            #二、j-i<2指i与j是同一个字符或者隔一个字符;p[i+1][j-1]指字符串:0、1...i、i+1...j-1、j...n-1中从i+1字符到j-1字符,也就是i、j之间的部分是回文串
            if arr[i]==arr[j] and (j-i<2 or p[i+1][j-1]):
                p[i][j]=True
                #对于字符串末尾字符相同的部分来说,一直保持dp[i]=0
                dp[i]=min(dp[i],dp[j+1]+1)
            j+=1
        i-=1
    return dp[0]

str=input()
arr=[]
for i in str:
    arr.append(i)
print(minCut(arr))

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值