动态规划理论基础
解释:动态规划,英文:Dynamic Programming,简称DP;如果某一问题有很多重叠子问题,使用动态规划是最有效的。
动态规划五部曲:
1、确定dp数组(dp table)以及下标的含义;
2、确定递推公式;
3、dp数组如何初始化;
4、确定遍历顺序;
5、举例推导dp数组;
为什么要先确定递推公式,然后在考虑初始化呢?因为一些情况是递推公式决定了dp数组要如何初始化!
509. 斐波那契数 - 力扣(LeetCode)
做动态规划的题目,严格按照五部曲走,分别对应5步,然后写出对应的代码;
①确定dp数组以及下标的含义:
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
②确定递推公式:
文中已给:F(n) = F(n - 1) + F(n - 2)公式,则状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
③dp数组如何初始化;
文中已给:F(0) = 0,F(1) = 1,则dp[0]=0,dp[1]=1;
④确定遍历顺序;
dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的;
⑤举例推导dp数组;
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:0 1 1 2 3 5 8 13 21 34 55,如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的;
class Solution:
def fib(self, n: int) -> int:
# 首先排除边的情况
if n == 0:
return 0
# 创建dp表
dp = [0]*(n+1)
# 初始化dp数组
dp[0] = 0
dp[1] = 1
# 遍历顺序,从前向后,
for i in range(2, n+1):
# 确定状态转移公式
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
if __name__ == '__main__':
n = 2
res = Solution().fib(n)
print(res)
70. 爬楼梯 - 力扣(LeetCode)
①确定dp数组以及下标的含义:
dp[i]的定义为: 爬到第i层楼梯,有dp[i]种方法
②确定递推公式:
自己手推dp[1]=1,dp[2]=2,dp[3]=3,dp[4]=5,则状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
③dp数组如何初始化;
dp[0]是存在争议的,但是对于dp[1]=1,dp[2]=2是没有争议的,那就从i=3开始遍历;
④确定遍历顺序;
dp[i] = dp[i - 1] + dp[i - 2],表示从前向后遍历;
⑤举例推导dp数组;
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为5时,的时候,dp数组应该是如下的数列:1,2,3,5,8;
class Solution:
def climbStairs(self, n: int) -> int:
# 边缘判断
if n <= 1:
return n
# 定义一个空数组
dp = [0]*(n+1)
# 初始化参数
dp[1] = 1
dp[2] = 2
for i in range(3, n+1):
# 状态方程
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
if __name__ == '__main__':
n = 5
res = Solution().climbStairs(n)
print(res)
746. 使用最小花费爬楼梯 - 力扣(LeetCode)
①确定dp数组以及下标的含义:
dp[i]的定义为:到达第i台阶所花费的最少体力为dp[i]。-
②确定递推公式:
题目中解释到一旦你支付此费用,即可选择向上爬一个或者两个台阶。即可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1]。
dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
③dp数组如何初始化;
题目描述中明确说了 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 也就是说 到达 第 0 个台阶是不花费的,但从 第0 个台阶 往上跳的话,需要花费 cost[0]。
所以初始化 dp[0] = 0,dp[1] = 0;(因为还没有开始爬,开始爬开需要消耗)
④确定遍历顺序;
dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了
⑤举例推导dp数组;
拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,来模拟一下dp数组的状态变化,如下:
from typing import List
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [0]*(len(cost)+1)
# 初始化,表示从起点开始和第一个索引下开始不需要花费体力
dp[0] = 0
dp[1] = 0
for i in range(2, len(cost)+1):
# 选择其中花费体力较小的路径,加上当前步的花费,更新dp数组
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
# 返回达到楼顶的最小花费
return dp[len(cost)]
if __name__ == '__main__':
cost = [1,100,1,1,1,100,1,1,100,1]
res = Solution().minCostClimbingStairs(cost)
print(res)