Medium
题目描述
我们把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个玻璃杯,依此类推到第100层,每个玻璃杯都能装一杯香槟。
然后,一些香槟被倒在最顶层的玻璃杯中。当顶层的玻璃杯满了时,任何溢出的液体都会等量地流向紧挨着它左边和右边的玻璃杯。当这些玻璃杯也满了时,任何溢出的香槟都会等量地流向这些玻璃杯的左边和右边的玻璃杯,依此类推。(处于底层的玻璃杯的任何溢出的香槟都会流到地板上。)
例如,在倒了一杯香槟后,最顶层的玻璃杯满了。倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒了三杯香槟后,第二层的香槟满了,此时总共有3个满的玻璃杯。在倒了四杯香槟后,第三层中间的玻璃杯半满,两边的玻璃杯各自盛放四分之一的香槟。
现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟比例( i 和 j 都从0开始)。
示例 1:
输入: poured = 1, query_row = 1, query_glass = 1
输出: 0.00000
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻璃杯都是空的。
示例 2:
输入: poured = 2, query_row = 1, query_glass = 1
输出: 0.50000
解释: 我们在顶层(下标是(0,0))倒了两杯香槟后,有一杯量的液体溢出。下标为 (1,0) 的玻璃杯和下标为 (1,1) 的玻璃杯平分了这一杯液体,所以每个玻璃杯有一半的液体。
示例 3:
输入: poured = 100000009, query_row = 33, query_glass = 17
输出: 1.00000
提示:
0 <= poured <= 10^90 <= query_glass <= query_row < 100
解题思路
这是一道经典的动态规划问题,关键是模拟香槟从上往下流动的过程。
思路分析:
我们可以使用二维数组 dp[i][j] 表示第 i 行第 j 个玻璃杯中实际倒入的香槟量(可能超过1杯)。
算法流程:
- 初始化:第0行第0个杯子倒入所有香槟,即
dp[0][0] = poured - 逐层计算:对于每个杯子,如果其中的香槟量超过1,则将多余部分平均分给下一层的相邻两个杯子
- 状态转移:如果
dp[i][j] > 1,则向dp[i+1][j]和dp[i+1][j+1]各流出(dp[i][j] - 1) / 2 - 最终答案:
min(1.0, dp[query_row][query_glass])
优化思路:
- 空间优化:由于每层只依赖上一层,可以用滚动数组优化空间复杂度
- 剪枝:当某一层所有杯子都为空时,可以提前结束
这个算法的核心思想是模拟物理过程,通过动态规划记录每个位置的状态,最终得到查询位置的结果。
代码实现
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
vector<double> dp(101, 0.0);
dp[0] = poured;
for (int row = 0; row < query_row; row++) {
vector<double> next(101, 0.0);
for (int col = 0; col <= row; col++) {
if (dp[col] > 1.0) {
double overflow = (dp[col] - 1.0) / 2.0;
next[col] += overflow;
next[col + 1] += overflow;
}
}
dp = next;
}
return min(1.0, dp[query_glass]);
}
};
class Solution:
def champagneTower(self, poured: int, query_row: int, query_glass: int) -> float:
dp = [0.0] * 101
dp[0] = poured
for row in range(query_row):
next_dp = [0.0] * 101
for col in range(row + 1):
if dp[col] > 1.0:
overflow = (dp[col] - 1.0) / 2.0
next_dp[col] += overflow
next_dp[col + 1] += overflow
dp = next_dp
return min(1.0, dp[query_glass])
public class Solution {
public double ChampagneTower(int poured, int query_row, int query_glass) {
double[] dp = new double[101];
dp[0] = poured;
for (int row = 0; row < query_row; row++) {
double[] next = new double[101];
for (int col = 0; col <= row; col++) {
if (dp[col] > 1.0) {
double overflow = (dp[col] - 1.0) / 2.0;
next[col] += overflow;
next[col + 1] += overflow;
}
}
dp = next;
}
return Math.Min(1.0, dp[query_glass]);
}
}
var champagneTower = function(poured, query_row, query_glass) {
let dp = new Array(101).fill(0.0);
dp[0] = poured;
for (let row = 0; row < query_row; row++) {
let next = new Array(101).fill(0.0);
for (let col = 0; col <= row; col++) {
if (dp[col] > 1.0) {
let overflow = (dp[col] - 1.0) / 2.0;
next[col] += overflow;
next[col + 1] += overflow;
}
}
dp = next;
}
return Math.min(1.0, dp[query_glass]);
};
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(query_row²) |
| 空间复杂度 | O(query_row) |
说明:
- 时间复杂度:需要遍历前 query_row 行,每行最多有 query_row + 1 个杯子
- 空间复杂度:使用滚动数组优化,只需要 O(query_row) 的额外空间