套碗游戏的取碗游戏(二)

119 阅读3分钟

问题描述

小F正在玩一个套碗的游戏,每个碗都有一个编号,从1到n,它们从大到小被套在一根木棍上。小F只能从木棍上取最上面的碗,每次只能取一个。现在你需要计算总共有多少种取碗的顺序。

例如,对于2个碗,取碗的顺序可以是 2 1 或 1 2,这两种顺序都是合法的。而对于3个碗,给定顺序 3 1 2 不可能通过合法操作实现,因此该顺序不可行。


测试样例

样例1:

输入:M = 2
输出:2

样例2:

输入:M = 3
输出:5

样例3:

输入:M = 4
输出:14

问题理解

你有一个从大到小排列的碗堆,编号从1到n。每次只能取最上面的碗。你需要计算出所有可能的取碗顺序的数量。

数据结构选择

这个问题可以通过动态规划来解决。我们可以定义一个状态 dp[i] 表示有 i 个碗时的所有可能取碗顺序的数量。

算法步骤

  1. 初始化

    • dp[0] = 1,表示没有碗时只有一种取法,即什么都不做。
    • dp[1] = 1,表示只有一个碗时只有一种取法。
  2. 状态转移

    • 对于 i 个碗,我们可以从 i-1 个碗的状态推导出来。
    • 每次取碗时,我们可以选择取最上面的碗,然后剩下的碗的顺序数量就是 dp[i-1]
    • 由于每次取碗的顺序不同,我们需要累加所有可能的取法。
  3. 计算

    • 使用一个循环从 2 到 M 计算 dp[i]

理解动态规划状态转移

在这个问题中,dp[i] 表示有 i 个碗时的所有可能取碗顺序的数量。我们需要通过递推关系来计算 dp[i]

递推关系

假设我们现在有 i 个碗,编号从 1 到 i,并且它们从大到小排列在木棍上。我们需要计算所有可能的取碗顺序。

  1. 选择最上面的碗

    • 每次取碗时,我们可以选择最上面的碗。假设我们取了编号为 k 的碗(1 <= k <= i)。
  2. 剩余碗的排列

    • 取了编号为 k 的碗后,剩下的碗的编号是 1 到 k-1 和 k+1 到 i

    • 这些剩余的碗可以看作两个独立的子问题:

      • 编号为 1 到 k-1 的碗,有 k-1 个碗。
      • 编号为 k+1 到 i 的碗,有 i-k 个碗。
  3. 组合子问题的解

    • 对于每个 k,我们需要将这两个子问题的解组合起来。
    • 具体来说,dp[k-1] 表示 k-1 个碗的所有可能取法,dp[i-k] 表示 i-k 个碗的所有可能取法。
    • 因此,取了编号为 k 的碗后,所有可能的取法数量是 dp[k-1] * dp[i-k]
  4. 累加所有可能的取法

    • 对于每个 k,我们需要累加所有可能的取法数量。
    • 这就是 dp[i] += dp[j] * dp[i - 1 - j]; 的含义。

具体解释

  • dp[j] 表示 j 个碗的所有可能取法。
  • dp[i - 1 - j] 表示 i-1-j 个碗的所有可能取法。
  • dp[i] += dp[j] * dp[i - 1 - j]; 表示对于每个 j,我们将 j 个碗和 i-1-j 个碗的所有可能取法组合起来,累加到 dp[i] 中。
public class Main {
    public static long solution(int n) {
        long[] dp = new long[n + 1];
        dp[0] = 1; // 没有碗时只有一种取法
        dp[1] = 1; // 只有一个碗时只有一种取法

        for (int i = 2; i <= n; ++i) {
            for (int j = 0; j < i; ++j) {
                dp[i] += dp[j] * dp[i - 1 - j]; // 累加所有可能的取法
            }
        }

        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(solution(2) == 2);
        System.out.println(solution(3) == 5);
        System.out.println(solution(4) == 14);
    }
}