动态规划
- 1.最优子结构:问题的最优解由相关子问题的最优解组合而成。
- 2.边界:问题的边界,得到有限的结果
- 3.动态转移方程:问题每一阶段和下一阶段的关系。
动态规划需要满足两个条件:
- 问题的状态满足最优性原理。即最优子结构
- 问题的状态必须满足无后效性。即以前出现状况 和 以前状态的变化过程 不会影响 将来的变化。
动态规划题目特点:
- 1.计数:
- 有多少种方式走到右下角。
- 有多少种方法选出 k 个数使得和是 Sum。
- 2.求最大值最小值:
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
- 3.求存在性:
- 取石子游戏,先手是否必胜
- 能不能选出 k 个数使得和是 Sum。
动态规划组成部分:
- 1.确定状态:研究最优策略的最后一步;化成子问题。
- 2.转移方程:根据子问题定义直接得到。
- 3.初始条件和边界情况:边界考虑周全。
- 4.计算顺序:利用之前的计算结果。
常见题型:
1. 求最大最小值动态规划
- 题目描述:有三种硬币,分别面值2元,5元和7元,每种硬币都足够多,买一本书需要27元。如何用 最少 的硬币组合正好付清,不需要对方找钱? 如果拼不出来则返回-1。
- 解析: d p [ i ] dp[i] dp[i] 表示 i i i 元的书需要的最少硬币数,所以 d p [ i ] = m i n d p [ i − 2 ] + 1 , d p [ i − 5 ] + 1 , d p [ i − 7 ] + 1 dp[i]=min{dp[i-2]+1, dp[i-5]+1, dp[i-7]+1} dp[i]=mindp[i−2]+1,dp[i−5]+1,dp[i−7]+1 。初始条件 d p [ 0 ] = 0 dp[0]=0 dp[0]=0 ,即边界。
- 注意:注意 d p dp dp 初始化用无穷大 f l o a t ( ′ i n f ′ ) float('inf') float(′inf′),即如果拼不出则 d p dp dp 值为无穷大。
(1)递归解法:
def f(N):
if N == 0:
return 0
res = float('inf')
if N >= 2:
res = min(f(N-2)+1, res)
if N >= 5:
res = min(f(N-5)+1, res)
if N >= 7:
res = min(f(N-7)+1, res)
return res
# print(f(27))
(2) dp解法:
def coinChange(A, N):
dp = [0]*(N+1)
for i in range(1, N+1):
dp[i] = float('inf')
for j in range(len(A)):
if i >= A[j] and dp[i-A[j]] != float('inf'):
dp[i] = min(dp[i-A[j]]+1, dp[i])
if dp[N] == float('inf'):
return -1
return dp[N]
# print(coinChange([2,5,7], 27))
2. 计数型动态规划
- 题目描述:给定 m m m 行 n n n 列的网格,有一个机器人从左上角 ( 0 , 0 ) (0,0) (0,0) 出发,每一步可以向下或者向右走一步,问有多少种方式走到右下角?
- 解析: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示机器人有多少种方式从左上角走到 ( i , j ) (i,j) (i,j), d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i−1][j]+dp[i][j−1] 。边界:当 i i i 或 j j j 等0时, d p dp dp 等于1。
- 注意:注意m和n是行数和列数,返回时需减1。
def uniquePaths(m, n):
dp = [[0 for i in range(n)]for j in range(m)]
for i in range(m):
for j in range(n):
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
# print(uniquePaths(4, 3))
3. 存在型动态规划
- 题目描述:有 n n n 块石头分别在x轴的 0 , 1 , . . . , n − 1 0,1,...,n-1 0,1,...,n−1 位置,一只青蛙在石头0,想跳到石头 n − 1 n-1 n−1,如果青蛙在第 i i i 块石头上,它最多可以向右跳的距离 a i a_i ai,问青蛙能否跳到石头 n − 1 n-1 n−1 ?
- 示例:
输入:a = [2,3,1,1,4] 输出:True
输入:a = [3,2,1,0,4] 输出:False
- 解析:令 d p [ i ] dp[i] dp[i] 表示青蛙能否跳到石头 i i i 上,状态转移方程为 f [ i ] = O R 0 < = j < i ( d p [ j ] a n d ( j + a [ i ] > = i ) ) f[i] = OR_{0<=j<i}(dp[j] and (j + a[i]>=i)) f[i]=OR0<=j<i(dp[j]and(j+a[i]>=i)) , 初始条件 d p ( 0 ) = T r u e dp(0)=True dp(0)=True 。
def canJump(A):
n = len(A)
dp = [False]*n
dp[0] = True
for i in range(1, n):
for j in range(i):
if dp[j] and (j + A[j]) >= i: # 注意索引i与j不要搞错
dp[i] = True
break
return dp[n-1]
print(canJump([2,3,1,1,4]))
print(canJump([3,2,1,0,4]))
- 注:此题 使用贪心算法(Greedy)更优,时间复杂度为O(n)。