TL;DR
Converging two pointers, always moving the shorter line β O(n) time, O(1) space.
Approach 1 β Brute force: try every pair
Compute the area of all n(n-1)/2 pairs and keep the maximum.
class Solution:
def maxArea(self, height: list[int]) -> int:
n = len(height)
best = 0
for i in range(n):
for j in range(i + 1, n):
area = (j - i) * min(height[i], height[j])
if area > best:
best = area
return best
- Time:
O(n^2) β about 5 * 10^9 pairs at n = 10^5.
- Space:
O(1).
The constraints kill it: billions of pair evaluations blow far past any time limit; the problem is engineered to force a linear insight.
Approach 2 β Converging two pointers (greedy elimination)
The insight: start with the widest container β pointers at both ends. Every other pair is narrower, so it can only win by being taller. Now the key exchange argument: the area is capped by the shorter line. Keeping the shorter line and pairing it with any line further inward gives smaller width * height β€ current area β every such pair is provably no better, so all of them can be discarded at once by moving the shorter lineβs pointer inward. Each step eliminates one line from consideration; after n - 1 steps every potentially-optimal pair has been covered.
This is a greedy algorithm β at each step a locally-justified choice (drop the shorter line) is proven never to discard the global optimum.
class Solution:
def maxArea(self, height: list[int]) -> int:
left, right = 0, len(height) - 1
best = 0
while left < right:
width = right - left
if height[left] <= height[right]:
best = max(best, width * height[left])
left += 1
else:
best = max(best, width * height[right])
right -= 1
return best
Walkthrough on height = [1,8,6,2,5,4,8,3,7]:
| left | right | heights | width | area | best | move |
|---|
| 0 | 8 | 1 / 7 | 8 | 8 | 8 | left (1 β€ 7) |
| 1 | 8 | 8 / 7 | 7 | 49 | 49 | right (7 < 8) |
| 1 | 7 | 8 / 3 | 6 | 18 | 49 | right |
| 1 | 6 | 8 / 8 | 5 | 40 | 49 | left (tie) |
| 2 | 6 | 6 / 8 | 4 | 24 | 49 | left |
| 3 | 6 | 2 / 8 | 3 | 6 | 49 | left |
| 4 | 6 | 5 / 8 | 2 | 10 | 49 | left |
| 5 | 6 | 4 / 8 | 1 | 4 | 49 | left β pointers meet |
Return 49 β found at step 2 and never beaten, exactly the pair (1, 8) from the example.
On a tie (height[left] == height[right], as at indices 1 and 6 above) moving either pointer is safe: the current pairβs area is recorded first, and any strictly better pair must be strictly inside both, which the continued sweep still reaches.
- Time:
O(n) β each iteration retires one index for good.
- Space:
O(1).
Why not other classics?
There is no meaningful third approach here: sorting destroys the width information the area depends on, and divide-and-conquer or DP add nothing because the greedy exchange argument already yields a one-pass optimum. If an interviewer probes, the expected deliverable is the proof of the greedy move, not an alternative algorithm.
Common pitfalls
- Moving the pointer at the taller line (or the one that βlooks promisingβ) β the exchange argument only justifies discarding the shorter line; moving the taller one can skip the optimal pair.
- Using
max(height[i], height[j]) for the area β water is bounded by the shorter wall; height 0 lines make this mistake obvious ([0,5] holds 0, not 5).
- Computing area only when it improves, but before deciding which pointer to move β compute area every step; the optimal pair may appear mid-sweep (step 2 in the walkthrough) and never again.
- Confusing this with Trapping Rain Water β here exactly two lines form the container and the bars between them are ignored; trapping-rain-water sums water over every bar.
Pattern takeaway
Converging two pointers earn O(n) on pair-maximization problems when you can prove an elimination lemma: from the current pair, identify the endpoint that cannot participate in any better pair and retire it permanently. Start from the extreme (widest) configuration so one dimension only degrades, then argue the other dimension is capped by the endpoint you drop. When you can state βevery pair involving this element is β€ the current best,β a linear sweep silently covers all O(n^2) candidates.