题目
假设一对幼年兔子需要一个月长成成年兔子,一对成年兔子一个月后每个月都可以繁衍出一对新的幼年兔子。不考虑死亡的情况,问第 N 个月时共有多少对兔子?
这是一个典型的斐波那契数列问题,即
第一个月有一对兔子;
第二个月兔子长大了,但还是只有一对兔子;
第三个月多了一对幼年兔子,共有两对兔子了;
第四个月又生了一对幼年兔子,同时第三个月的幼年兔子长成了成年兔子;
……
那么兔子数量的规律即:1,1,2,3,5,8,13,21……
设定第 0 个月数量为 0,观察可得,从第 2 个月开始,每个月的兔子数量都等于前两个月兔子数量的和。这就是著名的斐波那契数列。
递归解法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
System.out.print("请输入月份:");
int n = new Scanner(System.in).nextInt();
System.out.println("兔子的对数为:" + getNums(n));
}
public static int getNums(int n) {
if (n <= 1) return n;
return getNums(n - 1) + getNums(n - 2);
}
}
这个比较简单易懂,设定 n 为 0 或者 1 的时候为递归的跳出条件,其余情况直接使用递归返回上两个月的数量和即可。
非递归解法
如果不使用递归,那我们需要开辟一块数据,来存储我们“前面月份”得到的数量。
我们使用 动态规划 的思想,即我们需要求解第 N 个月的兔子数量,依赖于求解第 N-1 个月和第 N-2 个月的兔子数量这两个子问题,我们可以自底向上的求解出每个子问题的解,并保存起来,然后最终求解第 N 个月的兔子数量时直接使用已知的子问题的解即可。
代码如下:
public static int getNums(int n) {
int[] nums = new int[n + 1];
nums[1] = 1; // 初始状态:nums[0] = 0, nums[1] = 1
for (int i = 2; i < nums.length; i++) {
nums[i] = nums[i - 1] + nums[i - 2];
}
return nums[n];
}
我们使用了 nums 这个数组保存了每个月兔子数量的值,其实我们也大可不必这么奢侈,在这个问题中我们其实只需要知道前两个月的情况即可,那么我们可以只创建一个长度为 2 的数组分别保存 N-2 个月和 N-1 个月的情况即可。
代码如下:
public static int getNums(int n) {
if (n <= 1) return n;
// 使用一个长度为 2 的数组存储前两位的值
int[] nums = new int[] { 0, 1 };
int result = 0;
for (int i = 2; i <= n; i++) {
result = nums[0] + nums[1];
nums[0] = nums[1];
nums[1] = result;
}
return result;
}
总结
使用非递归的解法(动态规划),效率上来说也会比递归好很多,不仅仅是因为递归会使得栈的深度变得很大,而且在递归的过程中,求解 getNums(n - 1) + getNums(n - 2) 时,在不同的 n 值下,还会产生很多重复的计算。而非递归的方式仅仅只是获取到之前的值使用,不存在重复的计算消耗。