The Container With Most Water problem is one of the most popular two-pointer interview questions. At first glance, a brute-force solution seems straightforward, but with a small observation, we can reduce the time complexity from O(n²)to O(n).
Let’s break it down step by step.
Problem Statement
You’re given an array heights where heights[i] represents the height of the i-th vertical bar.
Choose any two bars to form a container. The amount of water the container can hold is determined by:
- The shorter of the two bars.
- The distance between them.
Return the maximum amount of water that can be stored.
Example
Input: heights = [1,7,2,5,4,7,3,6]
Output: 36Visualization:
Height
7 | | |
6 | | | |
5 | | | | |
4 | | | | | |
3 | | | | | | |
2 | | | | | | | |
1 | | | | | | | | | |
--------------------------------
1 7 2 5 4 7 3 6The container formed by the bars with heights 7 and 6 (distance = 6) stores:
Area = min(7, 6) × 6 = 36Understanding the Formula
For any two bars:
area = min(height[left], height[right]) * (right - left)The minimum height determines the water level because water spills over the shorter side.
The width is simply the distance between the two indices.
Solution 1: Brute Force
The most straightforward approach is to try every possible pair.
from typing import List
class Solution:
def maxArea(self, heights: List[int]) -> int:
mArea = 0
for i in range(len(heights)):
for j in range(i + 1, len(heights)):
area = min(heights[i], heights[j]) * (j - i)
mArea = max(area, mArea)
return mAreaHow it works
For every index:
- Pick a second index after it.
- Compute the area.
- Keep track of the maximum.
Example
[1,7,2,5]Pairs checked:
(1,7)
(1,2)
(1,5)
(7,2)
(7,5)
(2,5)Every possible container is evaluated.
Complexity
- Time: O(n²)
- Space: O(1)
While this works, it becomes inefficient for larger arrays because we’re checking far too many combinations.
Can We Do Better?
Notice something interesting.
Suppose our pointers are here:
1 8 6 2 5 4 8 3 7
L RCurrent area:
min(1,7) × widthThe limiting factor is the height 1.
Even if we move the taller bar inward, the minimum height is still 1, while the width decreases.
That means the area can never increase.
So moving the taller bar is pointless.
Instead, we move the shorter bar, hoping to find a taller one.
This single observation leads to the optimal solution.
Solution 2: Two Pointers
We start with the widest possible container.
One pointer begins at the left.
The other begins at the right.
After computing the area:
- Move the shorter pointer inward.
- Repeat until the pointers meet.
from typing import List
class Solution:
def maxArea(self, heights: List[int]) -> int:
l, r = 0, len(heights) - 1
maxArea = 0
while l < r:
currentArea = min(heights[l], heights[r]) * (r - l)
maxArea = max(maxArea, currentArea)
if heights[l] < heights[r]:
l += 1
else:
r -= 1
return maxAreaWhy Move the Shorter Pointer?
Let’s say we have:
3 8
L RCurrent area:
min(3,8) × widthIf we move the right pointer:
3 5
L RThe width becomes smaller.
The minimum height is still at most 3.
The area cannot improve.
But if we move the left pointer:
7 8
L RNow we’ve found a taller left bar.
Although the width shrank slightly, the minimum height increased significantly, giving us a chance to find a larger area.
This is the key insight behind the two-pointer technique.
Dry Run
Input:
[1,7,2,5,4,7,3,6]
Left | Right | Heights | Width | Area | Max |
|---|---|---|---|---|---|
0 | 7 | (1,6) | 7 | 7 | 7 |
1 | 7 | (7,6) | 6 | 36 | 36 |
1 | 6 | (7,3) | 5 | 15 | 36 |
1 | 5 | (7,7) | 4 | 28 | 36 |
1 | 4 | (7,4) | 3 | 12 | 36 |
1 | 3 | (7,5) | 2 | 10 | 36 |
1 | 2 | (7,2) | 1 | 2 | 36 |
Final answer:
36Complexity Analysis
Brute Force
- Time: O(n²)
- Space: O(1)
Two Pointers
- Time: O(n)
- Space: O(1)
The two-pointer solution is significantly faster because each pointer moves at most n times.
Key Takeaways
- The container’s height is determined by the shorter bar.
- Starting with the widest container gives us the maximum possible width.
- Moving the taller pointer can never produce a better answer because the width decreases while the limiting height stays the same.
- Always move the shorter pointer in hopes of finding a taller bar that can compensate for the reduced width.
- This transforms a quadratic solution into a linear one, making it the optimal approach.
The biggest lesson from this problem isn’t just learning the two-pointer technique—it’s recognizing how a simple observation can eliminate unnecessary work. Instead of checking every possible pair, we use the properties of the problem to make a greedy decision at each step, resulting in an elegant O(n) solution.