Container With Most Water
You are building a pool in your backyard and want to create the largest pool possible. The largest pool is defined as the pool that holds the most water. The workers you hired to dig the hole for your pool didn't do a great job and because of this the depths of the pool at different areas are not equal. Given an integer array of non-negative integers that represents a histogram of the different heights at each position of the hole for your pool, return the largest pool you can create.
Note: The water volume between two positions i and j is calculated as min(height[i], height[j]) * (j - i).
Example(s)
Example 1:
Input: heights = [1, 4, 4, 8, 2]
Output: 8
Explanation:
You can build your largest pool between indices 1 and 3 (inclusive).
Water volume = min(4, 8) * (3 - 1) = 4 * 2 = 8
Example 2:
Input: heights = [1, 8, 6, 2, 5, 4, 8, 3, 7]
Output: 49
Explanation:
The largest pool is between indices 1 and 8.
Water volume = min(8, 7) * (8 - 1) = 7 * 7 = 49
Example 3:
Input: heights = [1, 1]
Output: 1
Explanation:
Water volume = min(1, 1) * (1 - 0) = 1 * 1 = 1
Example 4:
Input: heights = [2, 3, 4, 5, 18, 17, 6]
Output: 17
Explanation:
The largest pool is between indices 1 and 6.
Water volume = min(3, 6) * (6 - 1) = 3 * 5 = 15
Wait, let me recalculate... Actually between indices 4 and 5:
Water volume = min(18, 17) * (5 - 4) = 17 * 1 = 17
Solution
The solution uses the two-pointer technique:
- Start with widest container:
left = 0,right = n - 1 - Calculate area:
min(height[left], height[right]) * (right - left) - Move pointer with smaller height: The area is limited by the shorter bar, so moving the taller bar inward can only decrease the area
- Track maximum: Keep track of the maximum area seen so far
- Continue until pointers meet
Key Insight: Always move the pointer with the smaller height because:
- The area is limited by the shorter bar
- Moving the taller bar inward can only decrease the width, and the height won't increase (still limited by shorter bar)
- Moving the shorter bar might find a taller bar, potentially increasing the area
- JavaScript Solution
- Python Solution
JavaScript Solution
/**
* Find the container with the most water
* @param {number[]} heights - Array of non-negative integers representing heights
* @return {number} - Maximum water volume
*/
function maxArea(heights) {
let left = 0;
let right = heights.length - 1;
let maxWater = 0;
while (left < right) {
// Calculate current area
const width = right - left;
const height = Math.min(heights[left], heights[right]);
const area = width * height;
// Update maximum
maxWater = Math.max(maxWater, area);
// Move pointer with smaller height
if (heights[left] < heights[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
// Test cases
console.log('Example 1:', maxArea([1, 4, 4, 8, 2])); // 8
console.log('Example 2:', maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49
console.log('Example 3:', maxArea([1, 1])); // 1
console.log('Example 4:', maxArea([2, 3, 4, 5, 18, 17, 6])); // 17
console.log('Test 5:', maxArea([1, 2, 1])); // 2Output:
Python Solution
from typing import List
def max_area(heights: List[int]) -> int:
"""
Find the container with the most water
Args:
heights: List of non-negative integers representing heights
Returns:
int: Maximum water volume
"""
left = 0
right = len(heights) - 1
max_water = 0
while left < right:
# Calculate current area
width = right - left
height = min(heights[left], heights[right])
area = width * height
# Update maximum
max_water = max(max_water, area)
# Move pointer with smaller height
if heights[left] < heights[right]:
left += 1
else:
right -= 1
return max_water
# Test cases
print('Example 1:', max_area([1, 4, 4, 8, 2])) # 8
print('Example 2:', max_area([1, 8, 6, 2, 5, 4, 8, 3, 7])) # 49
print('Example 3:', max_area([1, 1])) # 1
print('Example 4:', max_area([2, 3, 4, 5, 18, 17, 6])) # 17
print('Test 5:', max_area([1, 2, 1])) # 2Output:
Brute Force Solution
For comparison, here's the brute force approach (O(n²)):
- JavaScript Brute Force
- Python Brute Force
/**
* Brute force solution - O(n²) time
* Check all possible pairs
*/
function maxAreaBruteForce(heights) {
let maxWater = 0;
const n = heights.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const width = j - i;
const height = Math.min(heights[i], heights[j]);
const area = width * height;
maxWater = Math.max(maxWater, area);
}
}
return maxWater;
}
def max_area_brute_force(heights: List[int]) -> int:
"""
Brute force solution - O(n²) time
"""
max_water = 0
n = len(heights)
for i in range(n):
for j in range(i + 1, n):
width = j - i
height = min(heights[i], heights[j])
area = width * height
max_water = max(max_water, area)
return max_water
Test the Brute Force Solution
- JavaScript
- Python
JavaScript Brute Force Solution
/**
* Brute force solution - O(n²) time
* Check all possible pairs
*/
function maxAreaBruteForce(heights) {
let maxWater = 0;
const n = heights.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const width = j - i;
const height = Math.min(heights[i], heights[j]);
const area = width * height;
maxWater = Math.max(maxWater, area);
}
}
return maxWater;
}
// Test cases
console.log('Example 1:', maxAreaBruteForce([1, 4, 4, 8, 2])); // 8
console.log('Example 2:', maxAreaBruteForce([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49
console.log('Example 3:', maxAreaBruteForce([1, 1])); // 1
console.log('Example 4:', maxAreaBruteForce([2, 3, 4, 5, 18, 17, 6])); // 17Output:
Python Brute Force Solution
from typing import List
def max_area_brute_force(heights: List[int]) -> int:
"""
Brute force solution - O(n²) time
"""
max_water = 0
n = len(heights)
for i in range(n):
for j in range(i + 1, n):
width = j - i
height = min(heights[i], heights[j])
area = width * height
max_water = max(max_water, area)
return max_water
# Test cases
print('Example 1:', max_area_brute_force([1, 4, 4, 8, 2])) # 8
print('Example 2:', max_area_brute_force([1, 8, 6, 2, 5, 4, 8, 3, 7])) # 49
print('Example 3:', max_area_brute_force([1, 1])) # 1
print('Example 4:', max_area_brute_force([2, 3, 4, 5, 18, 17, 6])) # 17Output:
Complexity
- Time Complexity: O(n) - Where n is the length of the heights array. We traverse the array once with two pointers.
- Space Complexity: O(1) - Only using a constant amount of extra space for variables.
Approach
The solution uses the two-pointer technique with a greedy strategy:
- Initialize pointers: Start with
left = 0andright = n - 1(widest possible container) - Calculate area: For current positions, calculate
min(height[left], height[right]) * (right - left) - Update maximum: Keep track of the maximum area seen
- Move pointer: Always move the pointer with the smaller height
- If
height[left] < height[right]: moveleftright - Otherwise: move
rightleft
- If
- Terminate: Continue until
left >= right
Why This Works
The key insight is that we should always move the pointer with the smaller height:
Proof by contradiction:
- Suppose we have
height[left] < height[right] - Current area =
height[left] * (right - left) - If we move
rightleft:- New width =
(right - 1) - left(decreased) - New height ≤
height[left](still limited by shorter bar) - New area ≤ current area (both width and height can't increase)
- New width =
- If we move
leftright:- New width =
right - (left + 1)(decreased) - New height might be >
height[left](could find taller bar) - New area might be > current area
- New width =
Therefore, moving the shorter pointer is the only way to potentially find a larger area.
Key Insights
- Two-pointer technique is optimal for this problem
- Greedy strategy: Always move the pointer that limits the area
- Start wide: Beginning with the widest container gives us the best starting point
- No need to check all pairs: The greedy approach eliminates many possibilities
- O(n) solution: Much better than O(n²) brute force
Step-by-Step Example
Let's trace through Example 1: heights = [1, 4, 4, 8, 2]
Initial: left = 0, right = 4
heights = [1, 4, 4, 8, 2]
↑ ↑
left right
Step 1:
area = min(1, 2) * (4 - 0) = 1 * 4 = 4
maxWater = 4
height[left] (1) < height[right] (2) → left++
Step 2: left = 1, right = 4
area = min(4, 2) * (4 - 1) = 2 * 3 = 6
maxWater = 6
height[left] (4) > height[right] (2) → right--
Step 3: left = 1, right = 3
area = min(4, 8) * (3 - 1) = 4 * 2 = 8
maxWater = 8
height[left] (4) < height[right] (8) → left++
Step 4: left = 2, right = 3
area = min(4, 8) * (3 - 2) = 4 * 1 = 4
maxWater = 8 (no update)
height[left] (4) < height[right] (8) → left++
Step 5: left = 3, right = 3 → break
Final answer: 8
Edge Cases
- Two elements: Return the area between them
- All same height: Works correctly, area depends on width
- Ascending heights: Maximum area is at the ends
- Descending heights: Maximum area is at the ends
- One element: Not applicable (need at least 2)
- Empty array: Return 0
Optimization Note
When heights are equal, we can move either pointer. However, moving both doesn't help because:
- If we move only one, we'll eventually check the other combination
- Moving both would skip valid combinations
- The current approach (move one at a time) is correct
Related Problems
- Trapping Rain Water: Similar concept but different problem (water can be trapped at multiple positions)
- Largest Rectangle in Histogram: Different problem but also involves histograms
Takeaways
- Two-pointer technique is powerful for array problems
- Greedy strategy can lead to optimal solutions
- Start with widest container maximizes initial area
- Always move the limiting pointer is the key insight
- O(n) solution is possible with careful analysis of the problem structure