题目描述
知识点
动态规划
实现
码前思考
-
这道题给我的first impression是斐波拉契数列,就是给人以一种从小推到大的那种感觉(其实,后面发现真的就是斐波拉契数列,哭);
-
于是乎,我就想到了动态规划,动态规划通常是递推和递归两种方式。。题目明确指出不能用递归,我佛了,那只能递推;
-
由于感觉是动态规划,先确定一下
dp
数组的涵义和递推开始的地方是什么?dp
数组的涵义:由于题目是求n个台阶的到达方式,通常dp
数组的涵义就是问题的求解对象,所以我们把dp
数组定义为i
个台阶的到达方式;- 递推开始的地方:由于题目的特性,我假设是dp[1]=1,dp[2]=2,有问题后面再改吧。
-
接下来就是最重要的寻找大问题与小问题的关系,也就是状态转移方程!
这个过程是一个玄学的过程,感觉对于我这种🧠cannot run fast的人,我就是先到处试关系,不断纠正和想新的方式。
我发现在i=3
,对于到达i
的方式,无非就是两大类——第一步是“1个台阶”;第一步是“2个台阶”。这样就顺利变成了这两大类相加,这两大类是啥呢?就是dp[i-1]和dp[i-2]🙂,over!!!这踏马不就是斐波拉契数列吗?哭了,果然我🧠不太好。。。
实现代码
//采用动态规划进行解题
//动态规划的核心之一是分解成子结构
//注意只要第一步不同,这个序列绝对不相同
//递推的开始是1和2
#include "bits/stdc++.h"
using namespace std;
const int maxn = 100;
//输入的整数
int n;
//动态规划数组
//数组下标从1开始
int dp[maxn];
int main(){
//只要有输入
while(~(scanf("%d",&n))){
//共享数据结构要进行初始化
//通过分析发现,不用进行初始化
fill(dp,dp+maxn,0);
//递归的开始
dp[1] = 1;
dp[2] = 2;
for(int i=3;i<=n;i++){
for(int j=1;j<=2;j++){
dp[i] += dp[i-j];
}
}
printf("%d\n",dp[n]);
}
return 0;
}
码后反思
-
关于这种由于开头或者结尾的种类可以确定,从而变成子问题求解的题目还挺多的,比如我们的LIS就是类似的吧。其实说到底,动态规划的一部分题目是这样的,因为你想呀,既然动态规划是要用子问题来解答,有时你只要把你这个大问题比子问题多的部分自己求出来,剩下的交给子问题就好了,这个多出来的部分,不就有可能是开头或者结尾吗?🙂
-
哈哈,看了《王道》的解答,发现它想到了 打表,是呀,这种一次计算,终身快速查询可以的!记住啦。
-
一个非常重要的一点—— 当N等于90的时候,可能会超出int的范围!所以用long long!!! 这是我没有考虑到的。。。修改代码如下:
//采用动态规划进行解题 //动态规划的核心之一是分解成子结构 //注意只要第一步不同,这个序列绝对不相同 //递推的开始是1和2 #include "bits/stdc++.h" using namespace std; const int maxn = 100; //输入的整数 int n; //动态规划数组 //数组下标从1开始 long long dp[maxn]; int main(){ //递归的开始 dp[1] = 1; dp[2] = 2; for(int i=3;i<=90;i++){ for(int j=1;j<=2;j++){ dp[i] += dp[i-j]; } } //只要有输入 while(~(scanf("%d",&n))){ printf("%lld\n",dp[n]); } return 0; }
-
所以说,写出了代码不要沾沾自喜,要去看别人怎么解题的,发现自己的不足,记住:你只是个菜鸡,你刷题是为了找到自己的不足,避免在机试中犯这种错误!
-
重新梳理一下动态规划的步骤——定义
dp
数组;定义递推/递归边界;状态转移方程。。。说起来简单,但是做起来真的玄学。 -
其实更符合动态规划的思想是最后一步是1个台阶还是两个台阶,这样更能体现动态规划的“连贯性”?
-
加油💪