我们现在重述一下问题:
一对兔子从出生后第三个月开始,每月生一对小兔子。小兔子到第三个月又开始生下一代小兔子。假若兔子只生不死,一月份抱来一对刚出生的小兔子,问1年中每个月各有多少对兔子。
我们首先枚举一下前几个月
我们发现:
第一个月有一对兔子
第二个月有一对兔子
第三个月有两对兔子
第四个月有三对兔子
第五个月有五对兔子
……
这其中的规律你发现了么?
是的没错,除了第一个月和第二个月只有一个以外,其他的月份,当前这一个月的兔子的对数等于前两个月兔子的对数的和,有了这个思想,我们就可以利用代码实现了。
我们首先采用递归法来实现
递归方法如下:
#include <iostream>
using namespace std;
int dg(int n) {
if (n == 1 || n == 2)
return 1;
else
return dg(n - 1) + dg(n - 2);
}
int main() {
int n;
for(int i=1;i<13;++i)
cout<<dg(i)<<endl;
}
但是我们仔细思考,这个递归的方法貌似有些计算是重复计算了,比如说当计算dg(5)的时候,我们需要计算dg(4),dg(3),dg(2),dg(1),而计算dg(4)的时候,计算了dg(3),dg(2),dg(1),这里的dg(3),dg(2),dg(1)就是重复计算的。
那么我们是否可以简化这个计算呢,很明显这题可以转化成为一个简单的动态规划(动态规划就是带记忆的递归)。
那么我们可以开一个数组,取名为dp,数组的长度为13,也就是dp[13],由于数组下标是从0开始的,但是由于人类的习惯,所以我选择开一个长度为13的数组,这样下标就能到12,也就是代表十二个月。
那么我们定义一下每个数组里面的值代表的是该月兔子的数量(以下标代替月份)
那么我们就可以得到代码如下:
#include <iostream>
using namespace std;
const int N=13;
int dp[N];
int main() {
int n;
dp[1]=1;
dp[2]=1;
cout<<dp[1]<<endl<<dp[2]<<endl;
for(int i=3;i<13;++i)
{
dp[i]=dp[i-1]+dp[i-2];
cout<<dp[i]<<endl;
}
return 0;
}
那么我们能否再继续优化呢
我们可以思考一下
每个月的兔子的数量只能前两个月的兔子的数量有关
我们假设第一个月的兔子的数量为first,第二个月的兔子的数量为second,第三个月的兔子的数量third。然后third=first+second,然后到了第四个月,第四个月的数量是第二个月的数量加上第三个月的兔子的数量,这时候我们可以把第二个月想象成为first,第三个月想象成为second,那么第四个月就等于first+second,那么我们怎么进行转化呢,其实我们可以进行覆盖,当第三个月的数量等于第一个月的数量加上第二个月的数量之后,我们这时候可以将second的值赋值给first,third的值赋值给second,这时候third=first+second,也就实现了滚动求值,我们再可以每一次获得到second的值之后将second输出即可
整个代码如下所示:
#include <iostream>
using namespace std;
int main() {
int first=1,second=1,third;
cout<<first<<endl<<second<<endl;
for(int i=3;i<13;++i)
{
third=first+second;
cout<<third<<endl;
first=second;
second=third;
}
return 0;
}
至此,整个代码就优化完毕,我们可以回顾一下整个流程,我们先进行了递归暴力求解,接下来就是利用dp来优化时间复杂度,最后再利用滚动数组来优化空间复杂度。