title: Champagne Tower slug: champagne-tower difficulty: medium leetcode_id: 799 leetcode_url: https://leetcode.com/problems/champagne-tower/ categories: - arrays - dynamic-programming patterns: - slug: dynamic-programming is_optimal: true function_signature: "def champagne_tower(poured: int, query_row: int, query_glass: int) -> float:" test_cases: visible: - input: { poured: 1, query_row: 1, query_glass: 1 } expected: 0.0 - input: { poured: 2, query_row: 1, query_glass: 1 } expected: 0.5 - input: { poured: 100000009, query_row: 33, query_glass: 17 } expected: 1.0 hidden: - input: { poured: 0, query_row: 0, query_glass: 0 } expected: 0.0 - input: { poured: 1, query_row: 0, query_glass: 0 } expected: 1.0 - input: { poured: 2, query_row: 0, query_glass: 0 } expected: 1.0 - input: { poured: 4, query_row: 2, query_glass: 1 } expected: 0.5 - input: { poured: 6, query_row: 2, query_glass: 0 } expected: 0.75 - input: { poured: 25, query_row: 6, query_glass: 1 } expected: 1.0 description: | We stack glasses in a pyramid, where the **first** row has `1` glass, the **second** row has `2` glasses, and so on until the 100th row. Each glass holds one cup of champagne. Then, some champagne is poured into the first glass at the top. When the topmost glass is full, any excess liquid poured will fall equally to the glass immediately to the left and right of it. When those glasses become full, any excess champagne will fall equally to the left and right of those glasses, and so on. (A glass at the bottom row has its excess champagne fall on the floor.) For example, after one cup of champagne is poured, the topmost glass is full. After two cups of champagne are poured, the two glasses on the second row are half full. After three cups of champagne are poured, those two cups become full — there are 3 full glasses total now. After four cups of champagne are poured, the third row has the middle glass half full, and the two outside glasses are a quarter full. Now after pouring some non-negative integer cups of champagne, return how full the `j`th glass in the `i`th row is (both `i` and `j` are 0-indexed). constraints: | - `0 <= poured <= 10^9` - `0 <= query_glass <= query_row < 100` examples: - input: "poured = 1, query_row = 1, query_glass = 1" output: "0.00000" explanation: "We poured 1 cup of champagne to the top glass of the tower (which is indexed as (0, 0)). There will be no excess liquid so all the glasses under the top glass will remain empty." - input: "poured = 2, query_row = 1, query_glass = 1" output: "0.50000" explanation: "We poured 2 cups of champagne to the top glass of the tower (which is indexed as (0, 0)). There is one cup of excess liquid. The glass indexed as (1, 0) and the glass indexed as (1, 1) will share the excess liquid equally, and each will get half cup of champagne." - input: "poured = 100000009, query_row = 33, query_glass = 17" output: "1.00000" explanation: "With a large amount of champagne poured, the glass at row 33, position 17 is completely full." explanation: intuition: | Imagine pouring champagne into the top of an actual glass pyramid at a wedding. The liquid flows naturally — filling each glass until it overflows, then spilling equally to the two glasses below it. The key insight is to **simulate this flow process** rather than trying to calculate the final state directly. Think of each glass as a container that can temporarily hold *any* amount of liquid (not just 1 cup). The amount in a glass represents the total champagne that has flowed into it. If this exceeds 1 cup, the glass keeps 1 cup and the excess spills equally to its two children below. This is a classic **simulation with dynamic programming** approach. We process the pyramid row by row, top to bottom. At each glass, we calculate how much liquid overflows and distribute it to the next row. The "state" we track is how much total liquid has flowed into each glass. Why does this work? Because champagne only flows downward. A glass in row `i` only receives liquid from row `i-1`, which we've already fully processed. This creates a natural order of computation — perfect for DP. approach: | We solve this using **Row-by-Row Simulation**: **Step 1: Initialise the DP table** - Create a 2D array `dp` where `dp[i][j]` represents the total champagne that has flowed into the glass at row `i`, position `j` - Set `dp[0][0] = poured` — all champagne starts at the top glass   **Step 2: Process each row from top to bottom** - For each glass at position `(i, j)`, check if it has more than 1 cup - If `dp[i][j] > 1`, calculate the overflow: `overflow = dp[i][j] - 1` - Distribute half the overflow to each child: `dp[i+1][j] += overflow / 2` and `dp[i+1][j+1] += overflow / 2` - The current glass keeps exactly 1 cup (it's full)   **Step 3: Return the answer** - After processing all rows up to `query_row`, return `min(1, dp[query_row][query_glass])` - The `min(1, ...)` ensures we return at most 1.0 (a glass can't be more than 100% full)   **Space Optimisation:** Since each row only depends on the previous row, we can optimise to O(row) space by only keeping two rows at a time. However, the O(row²) solution is clearer and sufficient given the constraint that `query_row < 100`. common_pitfalls: - title: Trying to Calculate Directly with Combinatorics description: | A tempting approach is to calculate how much champagne reaches each glass using Pascal's Triangle-style combinatorics. But this fails because: - Glasses have a **capacity limit** of 1 cup - Once full, a glass stops "receiving" more — it just passes overflow down - The overflow depends on glasses above being full, which creates complex dependencies The simulation approach elegantly handles all these cases without complex math. wrong_approach: "Combinatorial formula based on paths" correct_approach: "Simulate the overflow process row by row" - title: Forgetting the Capacity Limit description: | When tracking liquid in each glass, it's easy to forget that a glass can never hold more than 1 cup in the final answer. If you track overflow separately, make sure to cap the result. For example, if `dp[query_row][query_glass] = 2.5`, the answer is still `1.0` — the glass is full, and the excess has already been distributed downward. wrong_approach: "Return dp[query_row][query_glass] directly" correct_approach: "Return min(1.0, dp[query_row][query_glass])" - title: Off-by-One Errors in Row Processing description: | Row `i` has `i+1` glasses (0-indexed). When distributing overflow from glass `(i, j)`, the children are at positions `(i+1, j)` and `(i+1, j+1)`. Make sure you: - Process exactly `i+1` glasses in row `i` - Don't go beyond `query_row` (unnecessary computation) - Handle the boundary glasses correctly (they still have two children) key_takeaways: - "**Simulation over calculation**: When physical processes have complex rules (capacity limits, cascading effects), simulation is often cleaner than closed-form solutions" - "**Row-by-row DP**: When data flows in one direction (like gravity), process in that direction and each step only depends on the previous" - "**Track total flow, not final state**: By tracking how much liquid *enters* each glass (not just how much it holds), we naturally handle overflow distribution" - "**This pattern appears in**: water flow problems, Pascal's Triangle variants, and any cascading/spreading simulation" time_complexity: "O(query_row²). We process at most `query_row + 1` rows, and row `i` has `i + 1` glasses, giving us 1 + 2 + ... + (query_row + 1) = O(query_row²) operations." space_complexity: "O(query_row²). We store the champagne amount for each glass up to `query_row`. This can be optimised to O(query_row) using two-row DP, but O(query_row²) is acceptable given the constraint `query_row < 100`." solutions: - approach_name: Row-by-Row Simulation is_optimal: true code: | def champagne_tower(poured: int, query_row: int, query_glass: int) -> float: # dp[i][j] = total champagne that has flowed into glass (i, j) dp = [[0.0] * (query_row + 2) for _ in range(query_row + 2)] # All champagne starts at the top glass dp[0][0] = float(poured) # Process each row, distributing overflow to the row below for row in range(query_row + 1): for col in range(row + 1): # Calculate overflow (amount exceeding capacity of 1) overflow = dp[row][col] - 1.0 if overflow > 0: # Half goes to left child, half to right child dp[row + 1][col] += overflow / 2.0 dp[row + 1][col + 1] += overflow / 2.0 # Return how full the queried glass is (capped at 1.0) return min(1.0, dp[query_row][query_glass]) explanation: | **Time Complexity:** O(query_row²) — We iterate through a triangular portion of the pyramid. **Space Complexity:** O(query_row²) — We store the state for all glasses up to the query row. We simulate the champagne flow by tracking how much liquid enters each glass. When a glass overflows, we distribute the excess equally to its two children below. The final answer is capped at 1.0 since that's the maximum a glass can hold. - approach_name: Space-Optimised (Two Rows) is_optimal: false code: | def champagne_tower(poured: int, query_row: int, query_glass: int) -> float: # Only keep track of the current row current_row = [float(poured)] for row in range(query_row): # Next row has one more glass next_row = [0.0] * (row + 2) for col in range(len(current_row)): # Calculate overflow from current glass overflow = current_row[col] - 1.0 if overflow > 0: # Distribute to children in next row next_row[col] += overflow / 2.0 next_row[col + 1] += overflow / 2.0 current_row = next_row # Handle edge case: if query_row is 0, current_row is still [poured] if query_glass < len(current_row): return min(1.0, current_row[query_glass]) return 0.0 explanation: | **Time Complexity:** O(query_row²) — Same as the standard approach. **Space Complexity:** O(query_row) — We only store two rows at a time instead of the entire pyramid. This optimisation is useful when memory is constrained, but given the problem's constraint of `query_row < 100`, the standard O(query_row²) space solution is perfectly acceptable and easier to understand.