从斐波那契数列开始,0基础入门递归和动态规划

reference:
【1】为什么你学不会递归?告别递归,谈谈我的一些经验
【2】告别动态规划,连刷40道动规算法题,我总结了动规的套路
【3】动态规划 无痛理解
个人认为,递归和动态规划,尤其能体现计算机编程的魅力。
【4】使用斐波那契数列引入了动态规划的概念

什么是斐波那契数列?

斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34….,即第一项 f(1) = 1,第二项 f(2) = 1……,第n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。

大家大概在小学的时候就接触过类似于这样的,找数学规律的题目。而在计算机编程中,这里的“规律”,就是我们编码的核心和基本。也叫做 「子问题」「状态」
例如上述的斐波那契数列,他的”规律“就是f(n) = f(n-1) + f(n-2)。
表示如下::
f ( n ) = { 1 n<=2 f ( n − 1 ) + f ( n − 2 ) n>2 f(n)= \begin{cases} 1 & \text{n<=2}\\ f(n-1) + f(n-2)& \text{n>2} \end{cases} f(n)={1f(n1)+f(n2)n<=2n>2

递归是什么?

“递归”, 可以从字面意思理解,就是 「传递」「回归」。所以递归最大的两要素就是 「传递规律」「回归条件」
例如,用递归来实现斐波那契数列。

一、先理解和处理函数程序的输入输出:输入是int n, 输出是int 第n项的值

public int fiboseq(int n){

}

二、找到 「回归」 条件,也就是函数的结束条件。

public int fiboseq(int n){
// n减到什么时候不再做递归
	if(n<=2){
		return 1;
	}
}

三、补充递归函数内核 「传递」 规律

public int fiboseq(int n){
// n减到什么时候不再做递归
	if(n<=2){
		return 1;
	}
// n的传递规律
	return fiboseq(n-1) + fiboseq(n-2);
}

我们可以亲切的把斐波那契数列问题看成鸡生蛋的问题。小鸡家族的每一只鸡都有一个使命,那就是自己生的蛋必须比前两只鸡生的蛋的总和。
现在我们是第n只小鸡,我们不知道我们应该生多少只蛋,于是我去问前面两只鸡 n-1n-2

return fiboseq(n-1) + fiboseq(n-2);

n-1只小鸡和第n-2只小鸡说我也忘了我生了多少蛋了,我去问问我前面那两只鸡。
进入到

fiboseq(n-1) + fiboseq(n-2);

的内部:

fiboseq(n-1)//第n-1只小鸡去问自己前面的第(n-1)-1只小鸡和第(n-1)-2只小鸡
return fiboseq((n-1-1) + fiboseq((n-1-2);
//第n-2只小鸡去问自己前面的第(n-2)-1只小鸡和第(n-2)-2只小鸡
fiboseq(n-2)return fiboseq((n-2-1) + fiboseq((n-2-2);

直到找到了小鸡祖宗,也就是第1只和第2只鸡,

fiboseq(2):
public int fiboseq(int n){
//我就是第二只鸡啦,我生了一只鸡
	if(n<=2){
		return 1;
	}
。。。
}
fiboseq(1):
public int fiboseq(int n=2){
//我就是第一只鸡啦,我生了一只鸡
	if(n<=2){
		return 1;
	}
。。。
}

好的,那么现在第三只鸡知道自己生了1+1=2只鸡了,第四只鸡也知道自己生了1+2=3只鸡啦,第五只也知道。。。最后来到了第n只,也就知道了自己生了多少只鸡啦!
由此可见编程是多么的神奇,如此复杂的一层一层向下寻找,又最后一层一层的向上回传,用寥寥几行代码就可以表示。

什么时候会用到递归?

同时,我们也可以总结出,当数据向下层寻找的时候若规律相同,即可以无限地调用起自己所在的函数的时候,就可以用递归。

动态规划又是什么?

以n=8为例,上面的函数可以简化为下面的模型:
在这里插入图片描述
在这里插入图片描述

此时的1⃣️号🐔和2⃣️号🐔内心OS:你们这群鸡崽子能不能记点事儿啊?比如3⃣️号🐔,你来问自己生了多少蛋问了七次,牛教三次都知道打转呢!(此段责骂的reference是我的妈妈)。
但是这时候的3⃣️号🐔内心非常的委屈:可是人家只是一只鸡,记不住那么多事情嘛。所以,不如我把我生了多少只鸡写下来,方便四号查看,四号之后是五号。。。这样我就只用问两个鸡祖宗一次,就不会被骂啦!
于是乎,就有了 「动态规划」 ,与 「递归」「自上而下」 不同, 「动态规划」「自下而上的」。在函数里,我们用for循环,来实现n的向上传递。

public int fiboseq(int n){
// 两个鸡祖宗依然作为开山鼻祖
	if(n<=2){
		return 1;
	}
	int[] dp = new int[n+1];//设置一个缓存空间
	//dp[0] = 0;
	dp[1] = 1;
	dp[2] = 2;
// 从三号鸡开始往上递送消息
	for(int m=3, m<=n; m++){
		dp[m] = dp[m-1]+dp[m-2];
	}
	return dp[n];
}

对于一个递归结构的问题,如果我们在分析它的过程中,发现了它有很多“重叠子问题”,虽然并不影响结果的正确性,但是我们认为大量的重复计算是不环保,不简洁,不优雅,不高效的,因此,我们必须将“重叠子问题”进行优化,优化的方法就是“加入缓存”,“加入缓存”的一个学术上的叫法就是“记忆化搜索”。

另外,我们还发现,直接分析递归结构,是假设更小的子问题已经解决给出的实现,思考的路径是“自顶向下”。但有的时候,“自底向上”的思考路径往往更直接,这就是“动态规划”,我们是真正地解决了更小规模的问题,在处理更大规模的问题的时候,直接使用了更小规模问题的结果。
一维动态规划练习题
leetcode
#70 爬楼梯
#746. 使用最小花费爬楼梯
#198 打劫家舍
#413 等差数列划分

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值