这道“兔群繁殖之谜”的问题,是我在参加厦大考研复试时C语言考试中遇到的一道题。当时,由于对动态规划(Dynamic Programming, DP)的理解还不够深入,我只能采用传统的数学推导方法,试图通过观察数列找到通项公式来解决问题。这种方法虽然也能得出答案,但计算的复杂度较高,并且在面对更加复杂的问题时显得力不从心。现在回过头来看,当时的方法确实显得有些笨拙。
在学习和掌握了动态规划算法之后,这道题目变得更加直观且易于求解。动态规划是一种将问题分解为子问题,通过保存子问题的计算结果,避免重复计算,从而提高效率的算法。它尤其适合求解具有“最优子结构”和“重复子问题”特性的问题,而“兔群繁殖之谜”就是这样一个典型的例子。
动态规划的核心思想
动态规划的关键在于明确状态的定义和状态转移方程。状态的定义可以理解为“子问题的描述”,而状态转移方程则是各状态之间的逻辑联系。对于本题来说,我们需要深入分析问题的规律:
- 兔子在出生后的第二个月才能开始繁殖,这意味着每只兔子至少需要一个月的“成长期”。
- 每个月的兔子状态可以根据上一个月的兔子状态推导出来。
问题状态的定义
为了更直观地解决问题,我们可以将兔子的状态划分为三种:
- 状态0:新出生的兔子。这些兔子在当前月刚出生,还不能繁殖。
- 状态1:成长期的兔子。这些兔子已经存活了一个月,下个月将开始繁殖。
- 状态2:成熟的兔子。这些兔子已经具备繁殖能力,每个月都可以生出新的兔子。
我们用三个变量表示这三种状态下的兔子数量:dp[0]表示新出生的兔子数量,dp[1]表示成长期的兔子数量,dp[2]表示成熟的兔子数量。
动态规划的推导公式
基于以上状态的定义,我们可以推导出状态转移方程:
- 新出生的兔子(dp[0]) :每个月,新出生的兔子数量等于上个月成熟兔子的数量,即
dp[0] = dp[2]。 - 成长期的兔子(dp[1]) :上个月新出生的兔子进入成长期,即
dp[1] = dp[0]。 - 成熟的兔子(dp[2]) :上个月的成熟兔子和成长期的兔子都在本月成为成熟兔子,即
dp[2] = dp[1] + dp[2]。
通过这三个状态转移公式,我们可以从初始状态逐步迭代出每个月兔子总数量的变化。
动态规划的实现
在实现时,我们只需初始化第一个月的兔子状态,假设初始有一只成熟兔子:dp[0] = 0, dp[1] = 0, dp[2] = 1,然后按月迭代更新状态即可。最终,任意一个月的兔子总数为 dp[0] + dp[1] + dp[2]。
public class Main {
public static long solution(int A) {
// Edit your code here
//动态规划算法
//三个状态 0 新出生 1 代表活了一个月 接下来可以生 2 代表一定可以生
Long[][] dp = new Long[A+1][3];
dp[1][0] = 0L;
dp[1][1] = 0L;
dp[1][2] = 1L;
for(int i=2;i<A+1;i++){
dp[i][0] = dp[i-1][1]+dp[i-1][2];
dp[i][1] = dp[i-1][0];
dp[i][2] = dp[i-1][1]+dp[i-1][2];
}
return dp[A][0]+dp[A][1]+dp[A][2];
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(1) == 1L);
System.out.println(solution(5) == 8L);
System.out.println(solution(15) == 987L);
}
}