前言:不适合无任何基础的小白,需要先看视频搞懂原理,这是自己的刷题总结
这几道题是看这位博主告别动态规划的讲解,写了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))
分类讨论,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))