题目描述
思路解析动画文字版
记住口诀:「满 1 才溢,溢出平分给下面两杯」,下面逐杯套它。
先把全部 8 杯香槟倒进顶杯:第 0 行只有 1 个杯子,里面是 8(紫)。它远远超过 1,接下来会大量溢出。其余行暂时都是空的(0)。
现在看第 0 行(蓝)的每个杯子。凡是超过 1 的,多出来的部分要平分流进第 1 行紧挨它的左右两杯。第 1 行此刻还是空的,等着接香槟。
看第 0 行第 0 个杯子(紫),里面有 8,超过 1。多出来的 8 - 1 = 7 平分成两份,每份 3.5,分别流向下一行第 0、第 1 个杯子。
把 3.5 同时灌进第 1 行的第 0 杯和第 1 杯(绿,它们现在分别累计到 3.5 和 3.5)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
第 0 行所有杯子都处理过了。第 1 行现在是 [3.5, 3.5](绿)。继续往下一行递推。
现在看第 1 行(蓝)的每个杯子。凡是超过 1 的,多出来的部分要平分流进第 2 行紧挨它的左右两杯。第 2 行此刻还是空的,等着接香槟。
看第 1 行第 0 个杯子(紫),里面有 3.5,超过 1。多出来的 3.5 - 1 = 2.5 平分成两份,每份 1.25,分别流向下一行第 0、第 1 个杯子。
把 1.25 同时灌进第 2 行的第 0 杯和第 1 杯(绿,它们现在分别累计到 1.25 和 1.25)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
看第 1 行第 1 个杯子(紫),里面有 3.5,超过 1。多出来的 3.5 - 1 = 2.5 平分成两份,每份 1.25,分别流向下一行第 1、第 2 个杯子。
把 1.25 同时灌进第 2 行的第 1 杯和第 2 杯(绿,它们现在分别累计到 2.5 和 1.25)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
第 1 行所有杯子都处理过了。第 2 行现在是 [1.25, 2.5, 1.25](绿)。继续往下一行递推。
现在看第 2 行(蓝)的每个杯子。凡是超过 1 的,多出来的部分要平分流进第 3 行紧挨它的左右两杯。第 3 行此刻还是空的,等着接香槟。
看第 2 行第 0 个杯子(紫),里面有 1.25,超过 1。多出来的 1.25 - 1 = 0.25 平分成两份,每份 0.125,分别流向下一行第 0、第 1 个杯子。
把 0.125 同时灌进第 3 行的第 0 杯和第 1 杯(绿,它们现在分别累计到 0.125 和 0.125)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
看第 2 行第 1 个杯子(紫),里面有 2.5,超过 1。多出来的 2.5 - 1 = 1.5 平分成两份,每份 0.75,分别流向下一行第 1、第 2 个杯子。
把 0.75 同时灌进第 3 行的第 1 杯和第 2 杯(绿,它们现在分别累计到 0.875 和 0.75)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
看第 2 行第 2 个杯子(紫),里面有 1.25,超过 1。多出来的 1.25 - 1 = 0.25 平分成两份,每份 0.125,分别流向下一行第 2、第 3 个杯子。
把 0.125 同时灌进第 3 行的第 2 杯和第 3 杯(绿,它们现在分别累计到 0.875 和 0.125)。同一个下层杯子可能同时接到左上、右上两个杯子的溢出,所以用累加。
第 2 行所有杯子都处理过了。第 3 行现在是 [0.125, 0.875, 0.875, 0.125](绿)。已经推到查询的第 3 行,接下来直接读取第 2 个杯子并对 1 取 min。
推到查询的第 3 行,看第 2 个杯子(紫):里面累计到 0.875。杯子最多装 1 杯,所以取 min(1, 0.875) = 0.875。这就是最终答案。
回顾整条链:8 杯从顶杯一路溢下来,每层「满 1 才溢、溢出平分给下面两杯」。查询的杯子(紫)最终是 0.875。整道题的核心就是从上到下逐行模拟这一个溢出规则。
边界:倒 0 全是 0;查第 0 行就是 min(1, poured);倒太多则该杯封顶为 1。
两个追问:滚动数组靠「只依赖上一行」;模拟途中不能提前封顶,只在读答案时取 min。
参考代码
class Solution: def champagneTower(self, poured: int, query_row: int, query_glass: int) -> float: row = [float(poured)] for _ in range(query_row): nxt = [0.0] * (len(row) + 1) for i, x in enumerate(row): over = max(0.0, x - 1.0) / 2.0 nxt[i] += over nxt[i + 1] += over row = nxt return min(1.0, row[query_glass])复杂度
- 时间:O(row²),从第 0 行推到第 query_row 行,第 r 行有 r+1 个杯子,累加是 1+2+…+row 个等差,约 row² / 2
- 空间:O(row),滚动数组只存当前行,最长就是查询行的 row+1 个杯子
易错点
面试追问把动画讲成自己的话
追问为什么可以用滚动数组,而不必存整座塔?
追问中间某个杯子的累计值超过 1,会不会影响下面的计算?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
石子游戏
LeetCode 877 · 中等 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题