A clear explanation of simulating water droplets over an elevation map by checking left first, then right.
Problem Restatement
We are given an elevation map as an integer array heights.
The value heights[i] is the current height at index i. Each index has width 1.
We are also given:
volume
kvolume units of water fall at index k, one unit at a time.
Each unit of water follows these rules:
- It first tries to move left.
- If moving left can eventually make it fall to a lower level, it settles at the best left position.
- Otherwise, it tries to move right.
- If moving right can eventually make it fall to a lower level, it settles at the best right position.
- Otherwise, it stays at index
k.
The level at an index means terrain height plus water already stored there.
Return the final heights array after all water has fallen.
Input and Output
| Item | Meaning |
|---|---|
| Input | heights, an integer array |
| Input | volume, the number of water units |
| Input | k, the index where water falls |
| Output | The final height array after pouring all water |
| Rule | Each water unit occupies exactly one index |
Example function shape:
def pourWater(heights: list[int], volume: int, k: int) -> list[int]:
...Examples
Example:
heights = [2, 1, 1, 2, 1, 2, 2]
volume = 4
k = 3Output:
[2, 2, 2, 3, 2, 2, 2]The first water unit falls at index 3.
It tries to move left. Since it can eventually reach a lower level on the left, it settles at index 2.
The second water unit also tries left first and settles at index 1.
The third water unit cannot fall left anymore, so it tries right and settles at index 4.
The fourth water unit cannot fall left or right, so it stays at index 3.
Final result:
[2, 2, 2, 3, 2, 2, 2]First Thought: Simulate the Water
This problem is a direct simulation.
The position of each new water unit depends on where previous water units settled.
So we process water one unit at a time.
For each unit, we decide where it settles, then increase that position by 1.
Key Insight
A droplet prefers the left side.
So for each water unit:
- Search left from
k. - If there is a lower reachable position, place the water at the leftmost best position.
- Otherwise, search right from
k. - If there is a lower reachable position, place the water at the right-side best position.
- Otherwise, place it at
k.
The phrase “eventually fall” matters.
A droplet can move across equal-height positions, but it only settles in a direction if it eventually finds a lower height.
For example:
heights = [3, 2, 2, 2]
k = 3From index 3, the water can move left across equal levels and eventually reach height 2, which is lower than height 3.
So moving left is allowed.
Finding the Left Position
Start at k.
Move left while the next position is not higher:
while i > 0 and heights[i - 1] <= heights[i]:
i -= 1This lets the droplet move across flat or downward terrain.
After this scan, i is the leftmost reachable position before the path would need to climb.
But we only use this position if it is lower than the original index k.
if heights[i] < heights[k]:
heights[i] += 1
continueIf this condition is true, the droplet eventually falls left and settles there.
Finding the Right Position
If the droplet cannot fall left, we try the right side.
Start again at k.
Move right while the next position is not higher:
while i + 1 < len(heights) and heights[i + 1] <= heights[i]:
i += 1Then we check whether this position is lower than k:
if heights[i] < heights[k]:
heights[i] += 1
continueIf yes, the droplet settles there.
If not, it stays at k.
Algorithm
- Repeat
volumetimes. - For each droplet:
- Set
i = k. - Move
ileft while the next left level is less than or equal to the current level. - If
heights[i] < heights[k], add water ati. - Otherwise, reset
i = k. - Move
iright while the next right level is less than or equal to the current level. - If
heights[i] < heights[k], add water ati. - Otherwise, add water at
k.
- Set
- Return
heights.
Correctness
Each droplet is handled independently, and the array is updated immediately after the droplet settles. Therefore, when the next droplet is processed, it sees the correct current levels.
For the left search, the algorithm moves from k left while the droplet can move to a position of equal or lower level. This exactly matches the rule that the droplet may move left without climbing. If the search ends at a position whose level is lower than the level at k, then moving left eventually makes the droplet fall, so the droplet must choose that side.
If the droplet cannot eventually fall left, the problem rule says it should try right. The right search applies the same logic in the right direction. If it finds a lower reachable position, the droplet settles there.
If neither side provides a lower reachable position, the rules say the droplet rises at its current position, which is index k. The algorithm then increments heights[k].
Since the algorithm applies the exact priority order for every droplet, and updates the level after each droplet, the final array is exactly the result of pouring all volume units of water.
Complexity
Let m = len(heights).
| Metric | Value | Why |
|---|---|---|
| Time | O(volume * m) | Each droplet may scan left and right across the array |
| Space | O(1) | The array is modified in place |
Implementation
class Solution:
def pourWater(self, heights: list[int], volume: int, k: int) -> list[int]:
for _ in range(volume):
i = k
while i > 0 and heights[i - 1] <= heights[i]:
i -= 1
if heights[i] < heights[k]:
heights[i] += 1
continue
i = k
while i + 1 < len(heights) and heights[i + 1] <= heights[i]:
i += 1
if heights[i] < heights[k]:
heights[i] += 1
continue
heights[k] += 1
return heightsCode Explanation
We process exactly one droplet per loop:
for _ in range(volume):For each droplet, we first search left:
i = k
while i > 0 and heights[i - 1] <= heights[i]:
i -= 1The condition heights[i - 1] <= heights[i] means the droplet can move left without climbing.
After the scan, we check whether this left position is actually lower than the starting level:
if heights[i] < heights[k]:
heights[i] += 1
continueIf so, the droplet settles there, and we move on to the next droplet.
If not, we search right:
i = k
while i + 1 < len(heights) and heights[i + 1] <= heights[i]:
i += 1Again, the droplet may move across equal or lower levels.
If the final right position is lower than index k, the droplet settles there:
if heights[i] < heights[k]:
heights[i] += 1
continueIf neither side works, the water stays at k:
heights[k] += 1Testing
def run_tests():
s = Solution()
assert s.pourWater([2, 1, 1, 2, 1, 2, 2], 4, 3) == [
2, 2, 2, 3, 2, 2, 2
]
assert s.pourWater([1, 2, 3, 4], 2, 2) == [
2, 3, 3, 4
]
assert s.pourWater([3, 1, 3], 5, 1) == [
4, 4, 4
]
assert s.pourWater([1, 1, 1], 3, 1) == [
2, 2, 1
]
assert s.pourWater([5], 3, 0) == [
8
]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Main example | Checks the standard left-first, then right behavior |
| Increasing slope | Water moves left before staying |
| Valley | Water fills the middle first, then sides |
| Flat terrain | Left preference matters |
| Single column | All water must stay at k |