11. Container With Most Water
Problem Description
You are presented with an integer array called height
which represents the heights of vertical lines placed at positions indexed from 1 to n (0-indexed in the array). Imagine that each height[i]
is linked to a line on a chart that extends upward from the x-axis to a point (i, height[i])
. Your task is to find two lines that, along with the x-axis, enclose the greatest possible area, which represents the maximum water that can be trapped between them without allowing any spillage over the sides of the lines (the container cannot be slanted). The goal is to calculate and return this maximum trapped water area.
Intuition
To solve this problem efficiently, we use a two-pointer technique. We place one pointer at the beginning of the array and the other at the end, and these pointers represent the potential container boundaries. At each step, the trapped water is determined by the distance between the pointers (which is the container's width) and the height of the smaller line (since the water level can't be higher than the smaller of the two boundaries). This is the area that could potentially be the maximum.
To maximize the area, after calculating the trapped water at each step and comparing it to the maximum we've seen so far, we move the pointer at the shorter line towards the other pointer. This is because keeping the pointer at the taller line stationary and moving the shorter one might lead us to find a taller line and thus a larger area. There's no advantage in moving the taller pointer first, as it would only reduce the potential width without guaranteeing a taller line to increase height. We repeat this process of calculating, updating the maximum water area, and moving the shorter line pointer towards the other pointer until the two pointers meet, at which point we've considered every possible container and the maximum stored water has been found.
By approaching this problem with each step optimized to either maintain or improve the potential maximum area, we are able to arrive at the solution efficiently, resulting in an algorithm that runs in linear time relative to the number of lines.
Learn more about Greedy and Two Pointers patterns.
Solution Approach
The implementation of the solution follows the two-pointer approach. Here's a step-by-step guide to how the solution works:
-
Initialize two pointers:
i
is set to the start of the array (0
), andj
is set to the end of the array (len(height) - 1
). -
Initialize a variable
ans
to keep track of the maximum area discovered so far. Initially,ans
is set to0
. -
Enter a loop that continues as long as
i
is less thanj
. This loop allows us to explore all possible combinations of lines fromi
toj
to maximize the area. -
Inside the loop, calculate the area trapped between the lines at pointers
i
andj
using the formula:area = (j - i) * min(height[i], height[j])
. This calculates the width of the container (j - i
) and multiplies it by the height, which is the smaller of the two heights atheight[i]
andheight[j]
. -
Update
ans
with the maximum of its current value and the calculated area.ans = max(ans, area)
ensures thatans
holds the highest value of trapped water area at each step. -
Determine which pointer to move. We need to move the pointer corresponding to the shorter line since this is the limiting factor for the height of the trapped water. We do this using a conditional statement:
- If
height[i] < height[j]
, then we incrementi
(i += 1
) to potentially find a taller line. - Else, decrement
j
(j -= 1
) for the same reason from the other end.
- If
-
Continue looping until the pointers meet. At this point,
ans
would have the maximum area that can be trapped between any two lines.
This solution uses a greedy approach, and its efficiency stems from the fact that at each stage, the move made is the best possible move to increase or at least maintain the potential of the maximum area. By incrementally adjusting the width and height of the considered container, it efficiently narrows down to the optimal solution.
The algorithm has a linear-time complexity, O(n), as each element is visited at most once, and there's a constant amount of work done within the loop.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using a small example.
Consider the integer array height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
.
-
We start with two pointers:
i
at the start (0
), representing the first line, andj
at the end (8
), representing the last line. Thus,i
points toheight[0]
which is1
, andj
points toheight[8]
which is7
. -
We set
ans = 0
, as we have not calculated any area yet. -
Now we start our loop where
i < j
. Since0 < 8
, we enter the loop. -
We calculate the area between lines at pointers
i
andj
. The width isj - i
which is8 - 0 = 8
, and the height is the smaller of two heights atheight[i]
andheight[j]
, somin(1, 7) = 1
. Thus, the area is8 * 1 = 8
. -
We update
ans
to be the maximum of its current value and the calculated area. So,ans = max(0, 8) = 8
. -
Since
height[i]
is less thanheight[j]
, we move thei
pointer to the right to potentially find a taller line. Nowi
becomes1
. -
Our two pointers now are at
i = 1
andj = 8
. We will continue this process untili
andj
meet. -
Repeat steps 4-6:
- New area at pointers
i = 1
andj = 8
:area = (8 - 1) * min(8, 7) = 7 * 7 = 49
. - Update
ans
to bemax(8, 49) = 49
. - Since
height[1]
is greater thanheight[8]
, we movej
to the left (nowj
is7
).
- New area at pointers
-
Continue iterations:
- New area at pointers
i = 1
andj = 7
:area = (7 - 1) * min(8, 3) = 6 * 3 = 18
. ans
remains49
since49 > 18
.height[1]
is greater thanheight[7]
, so we movej
to the left (nowj
is6
).
- New area at pointers
-
The process continues in this manner, always moving the pointer at the shorter height until
i
andj
are equal.
At the end of these iterations, ans
holds the maximum area that can be trapped, which in this example, is 49
. This is the largest amount of water that can be trapped between two lines without spilling.
Solution Implementation
1class Solution:
2 def maxArea(self, height: List[int]) -> int:
3 # Initialize two pointers, one at the beginning and one at the end of the height array
4 left, right = 0, len(height) - 1
5 # Initialize maximum area to 0
6 max_area = 0
7
8 # Use a while loop to iterate until the two pointers meet
9 while left < right:
10 # Calculate the area formed between the two pointers
11 current_area = (right - left) * min(height[left], height[right])
12 # Update the maximum area if current_area is larger
13 max_area = max(max_area, current_area)
14
15 # Move the pointer that points to the shorter line inward,
16 # since this might lead to a greater area
17 if height[left] < height[right]:
18 left += 1
19 else:
20 right -= 1
21
22 # Return the maximum area found
23 return max_area
24```
25
26Please note that the type hint `List[int]` requires importing `List` from the `typing` module in Python 3.5+. If you're using Python 3.9+ it's also possible to use built-in list type hints directly like `list[int]`. If you want to include the necessary import, here's how you would do that:
27
28```python
29from typing import List # This line is needed for the type hint (Python 3.5 - 3.8)
30
31class Solution:
32 # ... rest of the code remains the same
33
1class Solution {
2
3 // Method to find the maximum area formed between the vertical lines
4 public int maxArea(int[] height) {
5 // Initialize two pointers at the beginning and end of the array
6 int left = 0;
7 int right = height.length - 1;
8 // Variable to keep track of the maximum area
9 int maxArea = 0;
10
11 // Iterate until the two pointers meet
12 while (left < right) {
13 // Calculate the area with the shorter line as the height and the distance between the lines as the width
14 int currentArea = Math.min(height[left], height[right]) * (right - left);
15 // Update the maximum area if the current area is larger
16 maxArea = Math.max(maxArea, currentArea);
17
18 // Move the pointer that points to the shorter line towards the center
19 if (height[left] < height[right]) {
20 left++;
21 } else {
22 right--;
23 }
24 }
25
26 // Return the maximum area found
27 return maxArea;
28 }
29}
30
1#include <vector>
2#include <algorithm> // Include algorithm for std::min and std::max
3
4class Solution {
5public:
6 int maxArea(vector<int>& heights) {
7 int left = 0; // Starting from the leftmost index
8 int right = heights.size() - 1; // Starting from the rightmost index
9 int maxArea = 0; // Initialize the maximum area to 0
10
11 // Continue looping until the left and right pointers meet
12 while (left < right) {
13 // Calculate the current area with the minimum of the two heights
14 int currentArea = std::min(heights[left], heights[right]) * (right - left);
15 // Update the maximum area if the current area is larger
16 maxArea = std::max(maxArea, currentArea);
17
18 // Move the pointers inward. If left height is less than right height
19 // then we move the left pointer to right hoping to find a greater height
20 if (heights[left] < heights[right]) {
21 ++left;
22 } else { // Otherwise, move the right pointer to the left
23 --right;
24 }
25 }
26
27 return maxArea; // Return the maximum area found
28 }
29};
30
1function maxArea(height: number[]): number {
2 // Initialize two pointers, one at the start and one at the end of the array
3 let leftIndex = 0;
4 let rightIndex = height.length - 1;
5 // Initialize the variable to store the maximum area
6 let maxArea = 0;
7
8 // Iterate until the two pointers meet
9 while (leftIndex < rightIndex) {
10 // Calculate the area with the current pair of lines
11 const currentArea = Math.min(height[leftIndex], height[rightIndex]) * (rightIndex - leftIndex);
12 // Update maxArea if the current area is larger
13 maxArea = Math.max(maxArea, currentArea);
14
15 // Move the pointer that's at the shorter line inwards
16 // If the left line is shorter than the right line
17 if (height[leftIndex] < height[rightIndex]) {
18 ++leftIndex; // Move the left pointer to the right
19 } else {
20 --rightIndex; // Move the right pointer to the left
21 }
22 }
23
24 // Return the maximum area found
25 return maxArea;
26}
27
Time and Space Complexity
The given Python code implements a two-pointer technique to find the maximum area of water that can be contained between two lines, given an array of line heights.
Time Complexity
The function initializes two pointers at the start and end of the array respectively and iterates inwards until they meet, performing a constant number of operations for each pair of indices. Since the pointers cover each element at most once, the iteration is linear relative to the number of elements n
in the height
array.
Hence, the time complexity is O(n)
.
Space Complexity
The code uses a fixed number of integer variables (i
, j
, ans
, and t
) and does not allocate any additional memory that scales with the size of the input array.
Thus, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly using problem constraints.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!