动态规划(记忆化搜索):
将给定问题划分成若干子问题,直到子问题可以被直接解决。然后把子问题的答保存下来以免重复计算,然后根据子问题反推出原问题解的方法
动态规划也称为递推(暴力深搜+记忆中间状态结果)其中:
Problem One: 70. 爬楼梯
题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
输入:
输入一个整数表示台阶数 n
输出:
返回一个整数,表示爬到楼顶的方案数。
创建一个dp[]列表存储到达每一级台阶的方案数,使用for循环从小到大逐步求出所有台阶对应的方案数,最后返回dp[n-1]即可
时间复杂度:
O ( n ) O(n) O(n)
空间复杂度:
O ( n ) O(n) O(n)
class Solution:
def climbStairs(self, n: int) -> int:
if n == 0:
return 0
elif n == 1:
return 1
dp = [0]*(n+1)
dp[0], dp[1], dp[2] = 0, 1, 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[-1]
根据 动态规划 = 记忆化搜索 的理念可知,本题可以直接使用暴力搜索完成,但算法的复杂度有明显差异。
时间复杂度:
O ( 2 n ) O(2^n) O(2n)
空间复杂度:
O ( 1 ) O(1) O(1)
# 使用DFS直接搜索
# 时间复杂度:O(2的n次方)
def f(n)->int:
if n <= 0:
return 0
elif n == 1:
return 1
elif n == 2:
return 2
else :
return f(n-1)+f(n-2)
print(f(int(input())))
Problem Two: LCR 100. 三角形最小路径和
题目描述:
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
输入:
输入一个二维列表表示三角形
例如:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:
返回一个整数,表示最小路径和。
题目是一个标准的深度优先搜索问题,因为每一层中的元素会多次被使用,所以可以通过创建列表存储到达该点所需要的最小路径和(记忆化搜索)的方式提升效率。记忆化搜索也就是动态规划。
自然的方法是自上而下,找出每一层各元素到三角形上顶点的最小路径和,然后在最后一层中选出最小路径。但这种方法中每一层的元素需要分成左端点、中间节点和右端点三类,最后还需要遍历一整行找出最小值,算法的时间复杂度较高。
时间复杂度:
O ( n 2 ) O(n^2) O(n2)
空间复杂度:
O ( n ) O(n) O(n)
# 自上而下计算最小和
def f()->int:
# 读取输入
n = int(input())
tri = []
for i in range(n):
tri.append([int(j) for j in input().split()])
# 算法主体
if not tri:
# 如果列表为空,返回0
return 0
rows = len(tri)
dp = []
# 第一行特殊处理
dp.append([tri[0][0]])
for i in range(1, rows):
temp = []
for j in range(i+1):
if j == 0:
temp.append(tri[i][0]+dp[i-1][0])
elif j < i:
temp.append(tri[i][j]+min(dp[i-1][j-1], dp[i-1][j]))
else:
temp.append(tri[i][j]+dp[i-1][j-1])
dp.append(temp)
return min(dp[-1])
print(f())
算法优化:
通过对题目的观察可知题目不要求找出最短路径中的每一个元素,所以也可以自下而上的查找,存储每一个元素到最后一层的最小路径和。这样每一层只有一种节点(前驱节点一定是 tri[i+1][j] 和 tri[i+1][j+1] 二选一),不必区分左右端点,而且最后只需返回dp[0][0]即可,因为dp[0][0]存储的就是三角形上顶点到三角形最下层的最小路径和。
时间复杂度:
O ( n 2 ) O(n^2) O(n2)
空间复杂度:
O ( n ) O(n) O(n)
# 除了自上而下的计算,还可以自下而上地求出dp列表的值
# 在自下而上的过程中,三角形每一层都只有一种节点,更为简单
def f()->int:
n = int(input())
tri = []
for i in range(n):
tri.append([int(j) for j in input().split()])
# 算法主体
# 创建dp列表
dp = tri[:]
for row in range(n-2, -1, -1):
for col in range(row+1):
dp[row][col] = min(dp[row+1][col], dp[row+1][col+1]) + tri[row][col]
return dp[0][0]
print(f())
Problem Three: 信息学奥赛一本通 1301. 大盗阿福
题目描述:
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
这条街上一共有 N 家店铺,每家店中都有一些现金。阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
输入:
输入的第一行是一个整数T(T≤50) ,表示一共有T组数据。
接下来的每组数据,第一行是一个整数N(1≤N≤100,000) ,表示一共有N家店铺。第二行是N个被空格分开的正整数,表示每一家店铺中的现金数量。每家店铺中的现金数量均不超过1000。
输出:
对于每组数据,输出一行。该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
分析可知,被选中的两家店之间可能间隔1家店,也可能间隔2家店,但不可能间隔大于等于3家店,因为间隔的三家店中中间的那一家也可以同时被选中,如果不选,则该方案一定不是最优方案。所以本题实质上与爬楼梯相似,都是典型的动态规划题目。
创建一个列表dp记录从左向右打劫到每一个店铺时所能获得的最大金额,最后dp中的最大值即为所求。
时间复杂度:
O ( n ) O(n) O(n)
空间复杂度:
O ( n ) O(n) O(n)
def f(n, money):
# n = len(money)
if n == 1:
print(money[0])
return
elif n == 2:
print(max(money))
return
elif n == 3:
print(max(money[1], money[0]+money[2]))
return
# 初始化dp列表
dp = [0]*n
dp[0] = money[0]
dp[1] = max(money[0], money[1])
dp[2] = max(money[1], money[0] + money[2])
for i in range(3, n):
dp[i] = money[i] + max(dp[i-2], dp[i-3])
print(max(dp))
t = int(input())
for i in range(t):
f(int(input()), [int(k) for k in input().split()])