【专题】动态规划_金字塔的第 i 行有 i 个数字,每个数字等于它上方两个数字之和。
【题型】动态规划
1、斐波拉契数列(一维)
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你n ,请计算 F(n) 。
动态规划的解题代码块:数据量大的情况
由于结果可能很大,因此将结果对10007取模后输出。
def f(n): fib=[0]*100000 fib[1]=fib[2]=1 # 填充数组 for i in range(3,n+1): fib[i]=(fib[i-1]+fib[i-2]) % 10007 return fib[n]# 读取输入n = int(input())# 输出结果print(f(n))
简单递归的解题代码块:数据量小情况
def f(n): if n==0: return 0 elif n==1: return 1 else: return f(n-1)+f(n-2)a=f(4)print(a)----------------------------------输入:4输出:3解释:F(4) = F(3) + F(2) = 2 + 1 = 3----------------------------------
2、数塔《二维》✦✦✦
数塔就是由一堆数字组成的塔状结构,其中第一行1个数,第二行2个数,第三行3个数,依此类推。每个数都与下一层的左下与右下两个数相连接。这样从塔顶到塔底就可以有很多条路径可以走,现在需要求路径上的数字之和的最大值。
例如在上图中,5->8->12->10与5->3->16->11这两条路径上的数字之和都是35,是所有路径中的最大值。
输入:
第一行一个正整数n(1≤n≤20),表示数塔的层数。
接下来n行为数塔从上到下的每一层,其中第i层有i个正整数,每个数都不超过1000。
输出:
从塔顶到塔底的所有路径中路径上数字之和的最大值。
# 数塔的层数n=int(input())# [[5], [8, 3], [12, 7, 16], [4, 10, 11, 6]]tower = [list(map(int,input().split())) for _ in range(n)]# 从倒数第二层开始,向上逐层计算最大路径和for i in range(n-2,-1,-1): for j in range(i+1): tower[i][j] += max(tower[i+1][j],tower[i+1][j+1])print(tower[0][0])----------------------------输入: 4 5 8 3 12 7 16 4 10 11 6输出:35----------------------------
3、爬楼梯(一维)
我打算走楼梯上楼,共有n级台阶。
我身轻如燕,所以每次都可以选择上一级台阶或者两级台阶。
问有多少种上楼的方式。
基本思路:
一阶楼梯:1种方式
二阶楼梯:2种方式
三阶楼梯:3种方式
四阶楼梯:5种方式
即:f(n)=f(n-1)+f(n-2)的关系
递归法:
def climb_stairs(n): if n==1: return 1 elif n==2: return 2 else: return climb_stairs(n-1)+climb_stairs(n-2)# 示例输入n = int(input())print(climb_stairs(n)) # 输出: 3
动态规划法:
maxn=1024 # 假设最少有maxn台阶f=[0]*maxn # 达到n接台阶的总共方法def Climb(n): f[0]=f[1]=1 # 初始化 for i in range(2,n+1): # 到达第i台阶的方法数 = 到达第i-1台阶方法数 + 到达第i-2台阶方法数 f[i]=f[i-1]+f[i-2] return f[n]n=3print(Climb(n))----------------------------3----------------------------
延伸题:使用最小花费爬楼梯
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。
请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
maxn=1024 # 假设总共台阶数f=[0]*maxn # 到达i层的最小花费def minCostClimb(cost): f[0]=f[1]=0 # 初始化的两个台阶花费0 size=len(cost) for i in range(2,size+1): # 第i层的最小花费 = min(到达i-1层的最小花费 , 到达i-2层的最小花费) f[i]=min(f[i-1]+cost[i-1],f[i-2]+cost[i-2]) return f[size]costs=[10, 15, 20]print(minCostClimb(costs))---------------------------------15---------------------------------
4、连续子序列
4.1:最大连续子序列和(一维)
现有一个整数序列a1,a2,…,an,求连续子序列ai+…+aj的最大值。
n=int(input())# 序列长度s=list(map(int,input().split()))# 序列元素result=[0]*(n)def f(s): result[0]=s[0] lens=len(s) for i in range(1,lens): result[i]=max(s[i]+result[i-1],s[i]) print(i,result[i]) return sorted(result)new=f(s)print(new[-1])-----------------------------------输入:6-2 11 -4 13 -5 -2输出:20-----------------------------------
4.2:最长上升子序列(一维)
现有一个整数序列a1,a2,…,an,求最长的子序列(可以不连续),使得这个子序列中的元素是非递减的。输出该最大长度。
size=int(input()) # 序列长度lists=list(map(int,input().split())) # 序列元素# 初始化 dp 数组,dp[i] 表示以 a[i] 结尾的最长非递减子序列的长度,默认为1result=[1]*sizedef f(s): # 遍历每个元素 s[i] for i in range(len(s)): # 遍历 s[i] 之前的所有元素 s[j] for j in range(i): # 如果 s[j] <= s[i],则更新 result[i] if s[j]<=s[i]: result[i]=max(result[i],result[j]+1) return max(result) # 返回 result 数组中的最大值print(f(lists)) -----------------------------输入:71 2 3 -1 -2 7 9输出:5-----------------------------
类似题目:蓝桥勇士(一维)✦✦
小明是蓝桥王国的勇士,他晋升为蓝桥骑士,于是他决定不断突破自我。
这天蓝桥首席骑士长给他安排了 N 个对手,他们的战力值分别为 a1,a2,…,an,且按顺序阻挡在小明的前方。对于这些对手小明可以选择挑战,也可以选择避战。
作为热血豪放的勇士,小明从不走回头路,且只愿意挑战战力值越来越高的对手。
请你算算小明最多会挑战多少名对手。
n=int(input())a=list(map(int,input().split()))dp=[1]*(n+1)for i in range(1,n): for j in range(i): if a[j] < a[i]: dp[i] = max(dp[i],dp[j]+1)print(max(dp))------------------------------------输入: 6 1 4 3 2 5 6输出:4------------------------------------
类似题目:合唱队形(一维)✦✦
N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2,⋯K,他们的身高分别为 T1,T2,⋯,TK。 则他们的身高满足 T1<⋯Ti+1>⋯>TK(1≤i≤K)。
你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
n=int(input())a=list(map(int,input().split()))dp1=[0]*(n+1) # dp1[i]表示以i结尾的最长上升子序列长度dp2=[0]*(n+1) # dp2[i]表示以i出发的最长下降子序列长度# 最长上升子序列for i in range(1, n+1): dp1[i] = 1 # 初始化为1(至少包含自己) for j in range(1, i): # 遍历之前的元素(对应原数组索引0~i-2) # 如果当前元素比之前的元素大(注意这里原数组索引要减1) if a[i-1] > a[j-1]: dp1[i] = max(dp1[i], dp1[j] + 1) # 更新最长上升子序列长度# 最长下降子序列for i in range(n, 0, -1): dp2[i] = 1 for j in range(i + 1, n + 1): if a[i-1] > a[j-1]: dp2[i] = max(dp2[i], dp2[j] + 1)# 找最大的\"山峰\"长度:以i为峰顶,上升序列长度 + 下降序列长度 - 1(重复计算了顶点)ans = max((dp1[i] + dp2[i] - 1) for i in range(1, n + 1))print(n-ans)
4.3:最长公共子序列《二维》
现有两个字符串s1与s2,求s1与s2的最长公共子序列的长度(子序列可以不连续)。
s1 = input()s2 = input()result = 0# 获取字符串长度len1 = len(s1)len2 = len(s2)# 初始化dp二维数组【(len1+1) x (len2+1)】dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]for i in range(1, len1 + 1): for j in range(1, len2 + 1): if s1[i - 1] == s2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])print(dp[len1][len2])--------------------------------输入:sadstoryadminsorry输出:6--------------------------------
类似题目:蓝肽子序列
L 星球上的生物由蛋蓝质组成,每一种蛋蓝质由一类称为蓝肽的物资首尾连接成一条长链后折叠而成。
生物学家小乔正在研究 L 星球上的蛋蓝质。她拿到两个蛋蓝质的蓝肽序列,想通过这两条蓝肽序列的共同特点来分析两种蛋蓝质的相似性。
具体的,一个蓝肽可以使用 11 至 55 个英文字母表示,其中第一个字母大写,后面的字母小写。一个蛋蓝质的蓝肽序列可以用蓝肽的表示顺序拼接而成。
在一条蓝肽序列中,如果选取其中的一些位置,把这些位置的蓝肽取出,并按照它们在原序列中的位置摆放,则称为这条蓝肽的一个子序列。蓝肽的子序列不一定在原序列中是连续的,中间可能间隔着一些未被取出的蓝肽。
如果第一条蓝肽序列可以取出一个子序列与第二条蓝肽序列中取出的某个子序列相等,则称为一个公共蓝肽子序列。
给定两条蓝肽序列,找出他们最长的那个公共蓝肽子序列的长度。
题目大意:此处将蓝肽质完全分割为一个个蓝肽子序列, 然后再用动态规划算法求最大公共子序列
a=input()b=input()a1=[]temp=\"\"for i in range(len(a)): if ord(a[i])-ord(\'a\')<0: a1.append(temp) temp=\"\" if i==len(a)-1: temp+=a[i] a1.append(temp[:]) temp+=a[i]a1=a1[1:]b1=[]temp=\"\"for i in range(len(b)): if ord(b[i])-ord(\'a\')<0: b1.append(temp) temp=\"\" if i==len(b)-1: temp+=b[i] b1.append(temp[:]) temp+=b[i]b1=b1[1:]# 字符串可以视作数列[],如:“abc” => [\'a\',\'b\',\'c\']# 此处,每一个元素看做一个字母# a1 => [\'Lan\', \'Qiao\', \'Bei\']# b1 => [\'Lan\', \'Tai\', \'Xiao\', \'Qiao\']# 最长公共子序列len1=len(a1)len2=len(b1)dp=[[0]*(len2+1) for _ in range(len1+1)]for i in range(1,len1+1): for j in range(1,len2+1): if a1[i-1] == b1[j-1]: dp[i][j] = dp[i-1][j-1]+1 else: dp[i][j] = max(dp[i-1][j],dp[i][j-1])print(dp[len1][len2])---------------------------------------------输入:LanQiaoBei LanTaiXiaoQiao输出:2---------------------------------------------
5、背包问题《二维数组【模版】》
此处01背包和完全背包就基本够了。
01背包【每件物品只有一个】
【基本思想】:[0,i]物品任取并放入容量为j的背包
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
说明:一共有4件物品,背包容量为10【表格中间区域填写的是价值总和】
第二行为用空格隔开的n个整数wi(1≤wi≤10),表示物品重量;
第三行为用空格隔开的n个整数ci(1≤ci≤10),表示物品价值。
—— 重量=0 重量=1 重量=2 重量=3 重量=4 重量=5 重量=6 重量=7 重量=8 重量=9 重量=10 物品0 0 0 0 0 0 0 0 0 0 0 0 物品1【重量为2,价值为1】 0 0 1 1 1 1 1 1 1 1 1 物品2【重量为3,价值为3】 0 0 1 3 3 4 4 4 4 4 4 物品3【重量为4,价值为5】 0 0 1 3 5 5 6 8 8 9 9 物品4【重量为7,价值为9】 0 0 1 3 5 5 6 9 9 10 12 【理论】:新增一层的状态来源于上一层的状态。
n, V = list(map(int, input().split()))weights = list(map(int, input().split()))values = list(map(int, input().split()))def package(n, V, weights, values): temp = [[0] * (V + 1) for _ in range(n + 1)] # 【由于是从1开始遍历,因此实际物品需要j-1】 for i in range(1, n + 1): # 物品 for j in range(1, V + 1): # 当前背包容量 # 放不下,就是前一个物品的价值 if j < weights[i-1]: temp[i][j] = temp[i - 1][j] # 放得下 # (上一个价值) 与 (当前价值+【当前背包容量-物品容量】对应的价值) 的最大值 else: temp[i][j] = max(temp[i - 1][j], values[i-1] + temp[i - 1][j - weights[i-1]])# 返回最大价值 return temp[n][V]print(package(n, V, weights, values))---------------------------------------------输入: 4 10 2 3 4 7 # 物品重量 1 3 5 9 # 物品价值输出:12---------------------------------------------
【补充】使用dfs算法求解
使用场景:数据量少的情形
def DFS(idx, nowWeight, nowValue): global maxValue # 递归终止条件:已经考虑了所有物品 if idx == n: if nowValue > maxValue: maxValue = nowValue return # 选择不放入背包 DFS(idx + 1, nowWeight, nowValue) # 选择放入背包(如果不超过背包容量) if nowWeight + w[idx] <= maxWeight: DFS(idx + 1, nowWeight + w[idx], nowValue + c[idx])# 读取输入n, maxWeight = map(int, input().split())w = list(map(int, input().split()))c = list(map(int, input().split()))# 初始化最大价值maxValue = 0# 从第一个物品开始递归DFS(0, 0, 0)# 输出最大价值print(maxValue)--------------------------输入: 5 8 3 5 1 2 2 4 5 2 1 3输出:10--------------------------
完全背包【每件物品无限个】
有n种物品,每件物品无限个,每种物品的重量为wi,价值为ci。现在需要选出若干件物品放入一个容量为V的背包中(每种物品可以选任意次),使得在选入背包的物品重量之和不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值。
说明:一共有4件物品,背包容量为10【表格中间区域填写的是价值总和】
第二行为用空格隔开的n个整数wi(1≤wi≤10),表示物品重量;
第三行为用空格隔开的n个整数ci(1≤ci≤10),表示物品价值。
—— 重量=0 重量=1 重量=2 重量=3 重量=4 重量=5 重量=6 重量=7 重量=8 重量=9 重量=10 物品0 0 0 0 0 0 0 0 0 0 0 0 物品1【重量为2,价值为1】 0 0 1 1 2 2 3 3 4 4 5 物品2【重量为3,价值为3】 0 0 1 3 3 4 6 6 7 9 9 物品3【重量为4,价值为6】 0 0 1 3 6 6 7 9 12 12 13 物品4【重量为8,价值为10】 0 0 1 3 6 6 7 9 12 12 13 【理论】:新增一层的状态来源于上一层的状态。
n, V = list(map(int, input().split()))weights = list(map(int, input().split()))values = list(map(int, input().split()))def package(n, V, weights, values): temp = [[0] * (V + 1) for _ in range(n + 1)] # 【由于是从1开始遍历,因此实际物品需要j-1】 for i in range(1, n + 1): # 物品 for j in range(1, V + 1): # 当前背包容量 # 先考虑:继承上一级状态 temp[i][j] = temp[i-1][j] # 再考虑:要不要放 # (当前价值) 与 (当前价值+【当前背包容量-物品容量】对应的价值) 的最大值 if weights[i-1]<=j: temp[i][j] = max(temp[i][j], values[i-1] + temp[i][j - weights[i-1]])# 返回最大价值 return temp[n][V]print(package(n, V, weights, values))--------------------------------------------输入:4 10 2 3 4 8 1 3 6 10输出:13--------------------------------------------
6、最小消耗能量(一维)
现有n个从左至右摆放着的高台(编号为从1
到n
),每个高台有各自的高度hi。假设闯关者当前处于第i个高台,那么可以选择跳到第i+1或第i+2个高台(闯关者能够跳任意高度)。如果从第i个高台跳到第j个高台,那么将会消耗闯关者**|hj−hi|**点能量。问从第1个高台出发、到达第n个高台的过程中需要消耗的最小能量。
n=int(input())hights=list(map(int,input().split()))# 初始化dp=[0]*ndef f(hights): # 初始条件:到达第一个高台不需要能量 dp[0]=0 for i in range(1,n): # 从第 i-1 个高台跳过来的能量消耗 dp[i]=dp[i-1] + abs(hights[i]-hights[i-1]) # 如果可以从第 i-2 个高台跳过来,则取最小值 if i-2>=0: dp[i]=min(dp[i],dp[i-2] + abs(hights[i]-hights[i-2])) return dp[n-1]print(f(hights))----------------------------------输入:4 2 5 1 3输出:3----------------------------------
解释:
从第1
个高台出发,跳到第3
个高台,消耗|1−2|=1点能量;然后跳到第4
个高台,消耗|3−1|=2点能量。此方案消耗的能量最小,为1+2=3点。
7、矩阵最大权值《二维数组》
现有一个n∗m大小的矩阵,矩阵中的每个元素表示该位置的权值。现需要从矩阵左上角出发到达右下角,每次移动只能向右或者向下移动一格。求最后到达右下角时路径上所有位置的权值之和的最大值。
n,m=list(map(int,input().split()))a=[list(map(int,input().split())) for _ in range(n)]# 初始化dp数组【二维数组】dp=[[0]*m for _ in range(n)]def f(a,n,m): # 初始化起点 dp[0][0]=a[0][0] # 初始化第一行和第一列 for i in range(1,n): dp[i][0]=dp[i-1][0] + a[i][0] for j in range(1,m): dp[0][j]=dp[0][j-1] + a[0][j] # 状态转移方程 for i in range(1,n): for j in range(1,m): dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + a[i][j] return dp[n-1][m-1]print(f(a,n,m))------------------------------输入:2 3 1 4 3 2 8 5输出:18------------------------------
解释:
沿着1->4->8->5
的顺序移动可以得到最大权值1+4+8+5=18
。
8、不同路径《二维》
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
m,n=list(map(int,input().split()))def road(n,m): # 初始化【二维数组】 dp=[[0]*(n) for i in range(m)] # 临界值(初始化第一行和第一列) for i in range(m): dp[i][0] = 1 for j in range(n): dp[0][j] = 1 # 状态转移方程 for i in range(1,m): for j in range(1,n): dp[i][j] = dp[i-1][j] + dp[i][j-1] return dp[m-1][n-1]print(road(n,m))-------------------------------------输入:3 7 # 3行7列输出:28-------------------------------------
9、打家劫舍(一维)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
a=list(map(int,input().split()))# 房子个数size=len(a)dp=[0]*sizedef f(dp,size,a): # 边界性 if size==0: return 0 elif size==1: return a[0] #初始化 dp[0]=a[0] dp[1]=max(a[0],a[1]) # 将dp的第二个元素设置为第一二个房屋中的金额较大者 # 状态转移方程 for i in range(2,size): # 对于每个房屋,选择抢劫当前房屋 和 抢劫前一个房屋及其之前的最大金额 dp[i]+=max(a[i-1],a[i]+dp[i-2]) return max(dp)print(f(dp,size,a))-------------------------------------输入:2 7 9 3 1输出:12-------------------------------------
- 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。