问题描述
小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 个碗时的所有可能取碗顺序的数量。
算法步骤
-
初始化:
dp[0] = 1,表示没有碗时只有一种取法,即什么都不做。dp[1] = 1,表示只有一个碗时只有一种取法。
-
状态转移:
- 对于
i个碗,我们可以从i-1个碗的状态推导出来。 - 每次取碗时,我们可以选择取最上面的碗,然后剩下的碗的顺序数量就是
dp[i-1]。 - 由于每次取碗的顺序不同,我们需要累加所有可能的取法。
- 对于
-
计算:
- 使用一个循环从
2到M计算dp[i]。
- 使用一个循环从
理解动态规划状态转移
在这个问题中,dp[i] 表示有 i 个碗时的所有可能取碗顺序的数量。我们需要通过递推关系来计算 dp[i]。
递推关系
假设我们现在有 i 个碗,编号从 1 到 i,并且它们从大到小排列在木棍上。我们需要计算所有可能的取碗顺序。
-
选择最上面的碗:
- 每次取碗时,我们可以选择最上面的碗。假设我们取了编号为
k的碗(1 <= k <= i)。
- 每次取碗时,我们可以选择最上面的碗。假设我们取了编号为
-
剩余碗的排列:
-
取了编号为
k的碗后,剩下的碗的编号是1到k-1和k+1到i。 -
这些剩余的碗可以看作两个独立的子问题:
- 编号为
1到k-1的碗,有k-1个碗。 - 编号为
k+1到i的碗,有i-k个碗。
- 编号为
-
-
组合子问题的解:
- 对于每个
k,我们需要将这两个子问题的解组合起来。 - 具体来说,
dp[k-1]表示k-1个碗的所有可能取法,dp[i-k]表示i-k个碗的所有可能取法。 - 因此,取了编号为
k的碗后,所有可能的取法数量是dp[k-1] * dp[i-k]。
- 对于每个
-
累加所有可能的取法:
- 对于每个
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);
}
}