斐波那契数列:暴力递归改动态规划!
提示:本期开始仔细讲暴力递归改为动态规划填表问题
题目
求斐波那契数列f(n)
当n=1,或者n=2时,f(n)=1;
其余f(n)=f(n-1)+f(n-2)
一、审题
示例:
n=1,f=1
n=2,f=1
n=3,f=f(n-1)+f(n-2)=1+1=2
n=4,f=f(n-1)+f(n-2)=2+1=3
n=5,f=f(n-1)+f(n-2)=3+2=5
1 1 2 3 5 8 11 19 ……
二、暴力递归代码
这逻辑很简答
求斐波那契数列f(n):
当n=1,或者n=2时,f(n)=1;
其余f(n)=f(n-1)+f(n-2)
手撕太过于easy
//斐波那契数列:1,1,2,3,5,8
//f(n) = f(n-1) + f(n-2)
//假如n==100,根据测试,电脑开足马力,计算了将近1分钟还没有出来,我停止了
//n==45,耗时大概3秒
//而采用缓存的动态规划方法,直接秒杀出来结果节约了大量时间,刚刚100都没法求,而现在很快出来了
//如果是暴力递归的尝试:
public static int f(int n){
if (n == 1 || n == 2) return 1;
return f(n - 1) + f(n - 2);
}
public static int feibo1(int n){
if (n < 1) return 0;
return f(n);
}
测试结果:
public static void test(){
System.out.println(feibo1(45));
}
你在idea上跑一下,自己计时:
半天才出结果:
1134903170
说明什么,速度实在是太慢了!!!!!!!!!!
暴力递归改动态规划:傻缓存dp
上面为啥慢????
咱们来分析:
咱们当初求f(43)的时候,把f(42)求过一遍了
现在求f(44)又去把f(43)和f(42)再求一遍,岂不是浪费???
如果当初求f(43)和f(42)的时候,直接拿个表记忆一下,比如记录为dp(43)和dp(42)
现在求f(44) = dp(43)+dp(42),这样不用再去递归了,瞬间就返回,这样的话,速度贼快!!!
这个dp记忆信息的方法就叫动态规划,动态缓存数据,直接用,加速算法返回的速度,减少运算时间,当然代价就是额外空间!!!
手撕代码实现一下:
//复习斐波那契数列动态规划傻缓存
public static int f3(int n, int[] dp){
if (dp[n] != -1) return dp[n];//求过了,直接返回
if (n == 1) {
dp[1] = 1;
return dp[1];
}
if (n == 2) {
dp[2] = 1;
return dp[2];
}
dp[n] = f3(n - 1, dp) + f3(n - 2, dp);
return dp[n];
}
public static int feiBoReview(int n){
if (n < 1) return 0;
//傻缓存
int[] dp = new int[n + 1];
for (int i = 0; i < n + 1; i++) {
dp[i] = -1;//默认全-1
}
return f3(n, dp);
}
public static void test2(){
System.out.println(feibo2(45));
System.out.println(feiBoReview(45));
//这里有一个小问题,当你斐波那契数列数字为100时,int类型的结果已经超过65536这么大,所以结果也不好弄了
//要变化范围才行
//所以暂时计算就不要超过那个大结果
}
public static void main(String[] args) {
// test();
test2();
}
你计时看看,瞬间出来了
1134903170
1134903170
再尝试一下从左往右填表试试
不就一个n参数吗?
dp[n]就是我们要的结果,咱从1 2 3 --N-1填表,最后取dp[n]就行
手撕代码填表很简单!!!
//不就一个n参数吗?
//dp[n]就是我们要的结果,咱从1 2 3 --N-1填表,最后取dp[n]就行
public static int feiBoReviewDP2(int n){
if (n < 1) return 0;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 1;//初始化
for (int i = 3; i < n + 1; i++) {
dp[i] = dp[i - 1] + dp[i - 2];//转移方程,递推
}
return dp[n];
}
public static void test2(){
System.out.println(feibo2(45));
System.out.println(feiBoReview(45));
System.out.println(feiBoReviewDP2(45));
//这里有一个小问题,当你斐波那契数列数字为100时,int类型的结果已经超过65536这么大,所以结果也不好弄了
//要变化范围才行
//所以暂时计算就不要超过那个大结果
}
public static void main(String[] args) {
// test();
test2();
}
所谓转移方程,不就是暴力递归函数中的递归公式吗,咱们用傻缓存dp直接将表格从左往右填写,
填一张表的过程,就是动态规划最经典的代码!!!
结果:
1134903170
1134903170
1134903170
总结
提示:重要经验:
1)暴力递归到动态规划的思想,是非常巧妙的,dp表怎么填,都是根据暴力递归函数来的。
2)暴力递归尝试,从左往右,n++,只需一个变量,dp能动态暂存前面的信息,他们都是不变的,后续位置的表格依赖前面的结果,整个表格填完,最后返回dp[n],速度非常快,空间换时间,这就是dp动态规划的本质!!!
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。