前几天有点忙加上贪心后面好难QWQ 暂时跳过两天的贪心,开始学动归
动态规划理论基础:
文章链接:代码随想录
文章思维导图:
文章摘要:
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
动态规划的解题步骤(动归五部曲)
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
一些建议与解惑
一些同学可能想为什么要先确定递推公式,然后在考虑初始化呢?
因为一些情况是递推公式决定了dp数组要如何初始化!
做动归找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
力扣题部分:
509. 斐波那契数
题目链接:. - 力扣(LeetCode)
题面:
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1。
给定 n
,请计算 F(n)
。
思路:
动归五部曲:
1.确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2.确定递推公式
递推公式题目直接给我们了,我们可以写出状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
3.dp数组如何初始化
同样也是直接由题目得出dp[0] = 0, dp[1] = 1。
4.确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
5.举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
代码实现:
class Solution {
public:
int fib(int n) {
int dp[35];
if(n == 0 || n == 1) return n;
dp[1] = 1;
for(int i = 2; i <= n; i ++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
70. 爬楼梯
题目链接:. - 力扣(LeetCode)
题面:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:
动归五部曲:
1.确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
2.确定递推公式
如何可以推出dp[i]呢?
首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!
递推公式: dp[n] = dp[n - 1] + dp[n - 2];
3.dp数组如何初始化
回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]种方法
递推公式需要两个值,由题目不难得出 dp[1] = 1, dp[2] = 2。
4.确定遍历顺序
遍历顺序一定是从前向后遍历的,和上一题本质其实一样。
5.举例推导dp数组
1 2 3 5 8 13 21 34 55
不少人肯定看出来了,这不就是斐波那契数列么!
和上一题区别就是没前两个数组而已。
代码实现:
class Solution {
public:
int climbStairs(int n) {
int dp[50];
if(n == 1 || n == 2) return n;
dp[1] = 1, dp[2] = 2;
for(int i = 3; i <= n; i ++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
746. 使用最小花费爬楼梯
题目链接:. - 力扣(LeetCode)
题面:
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0
或下标为 1
的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
思路:
动归五部曲:
1.确定dp数组以及下标的含义
dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。
2.确定递推公式
可以有两个途径得到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]);
3.dp数组如何初始化
看一下递推公式,dp[i]由dp[i - 1],dp[i - 2]推出,既然初始化所有的dp[i]是不可能的,那么只初始化dp[0]和dp[1]就够了,其他的最终都是dp[0]dp[1]推出。
题目说你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。也就是说到达 第 0 个台阶是不花费的,但从 第0 个台阶往上跳的话,需要花费 cost[0]。
所以初始化 dp[0] = 0,dp[1] = 0;
4.确定遍历顺序
因为是模拟台阶,而且dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。
今天的三道题好像这个步骤不需要思考,但随着后面的学习,我们会遇到需要在这块做文章的题目的。
5.举例推导dp数组
拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,来模拟一下dp数组的状态变化,如下:
代码实现:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost)
{
int dp[1000] = {0};
for(int i = 2; i < cost.size(); i ++)
{
dp[i] = min((dp[i - 1] + cost[i - 1]), (dp[i - 2] + cost[i - 2]));
//cout<<dp[i]<<" ";
}
int ans = min(dp[cost.size() - 1] + cost[cost.size() - 1], dp[cost.size() - 2] + cost[cost.size() - 2]);
return ans;
}
};