刷题笔记:101兔群繁殖之谜 | 豆包MarsCode AI刷题

69 阅读3分钟

问题描述

生物学家小 R 正在研究一种特殊的兔子品种的繁殖模式。这种兔子的繁殖遵循以下规律:

  1. 每对成年兔子每个月会生育一对新的小兔子(一雌一雄)。
  2. 新生的小兔子需要一个月成长,到第二个月才能开始繁殖。
  3. 兔子永远不会死亡。

小 R 从一对新生的小兔子开始观察。他想知道在第 A 个月末,总共会有多少对兔子。

请你帮助小 R 编写一个程序,计算在给定的月份 A 时,兔子群体的总对数。

注意:

  • 初始时有 1 对新生小兔子。
  • 第 1 个月末有 1 对兔子:原来那对变成了成年兔子,并开始繁殖。
  • 第 2 个月末有 2 对兔子:原来那 1 对成年兔子,繁殖了 1 对新生的小兔子。
  • 从第 3 个月开始,兔子群体会按照上述规律增长。

思路分析

这是一个典型的动态规划问题,下面我将使用递推法来实现代码。

  • 对于兔子而言,它需要经过出生、成熟、繁殖三个过程,所以我们利用dp[A+1][3]来存储每日3类兔子的数量。
  • dp[n][0]表示第n天的幼兔数量,dp[n][1]表示第n天的成熟兔子数量,dp[n][2]表示第n天的成兔数量。
  • 只有成兔才能繁殖,定义状态转移方程,如下代码所示。
public class Main {
    public static long solution(int A) {
        // Edit your code here
        long[][] dp = new long[A + 1][3];
        if (A == 1) {
            return 1L;
        }
        dp[0][0] = 1L; // 幼兔
        dp[0][1] = 0L;
        dp[0][2] = 0L; // 成兔
        dp[1][0] = 0L;
        dp[1][1] = 1L;
        dp[1][2] = 0L;
        for (int i = 2; i <= A; 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);
    }
}

对于动态规划问题,常见的方法有两种,分别是备忘录法和递推法。下面我将解释这两种方法。

递推法(Tabulation / 迭代法)

定义:

  • 递推法是一种自底向上的方法,从最简单的情况开始逐步构建解,直到得到最终结果。
  • 它通常使用一个数组或表格来存储中间结果,并通过迭代的方式更新这些结果。

特点:

  1. 自底向上:从已知的初始状态开始,逐步计算出更复杂的状态,直到达到目标状态。
  2. 空间效率:通常只需要一个固定大小的数组来存储中间结果,空间复杂度较低。
  3. 易于理解和实现:逻辑清晰,容易理解,且不需要处理递归调用栈的问题。
  4. 适用于所有子问题:即使某些子问题不会被后续计算用到,也会被计算出来。

备忘录法(Memoization)

定义:

  • 备忘录法是一种自顶向下的方法,它通过递归来解决问题,并在递归过程中存储已经计算过的结果,以避免重复计算。
  • 它通常使用一个哈希表或数组来存储中间结果,并在递归调用时检查这些结果是否已经存在。

特点:

  1. 自顶向下:从目标状态开始,逐步分解为更简单的子问题,直到达到基本情况。
  2. 空间效率:可能需要额外的空间来存储递归调用栈,但对于某些问题,可以减少不必要的计算。
  3. 避免重复计算:通过缓存已经计算过的结果,避免了重复计算相同的子问题。
  4. 灵活性:只计算必要的子问题,对于某些问题可以提高效率。