Python Programming Challenge 19: Container with Most Water


In today’s post, we’ll discuss the Container with Most Water problem taken from Leetcode 11.

Container With Most Water

For this challenge, you are given a list of non-negative numbers. Each number in the list represents the height of vertical bars in a container.

For instance, if we have the list [2, 1, 10, 2, 5, 4, 8, 3, 7], the numbers represent the height of vertical bars shown below:

container with most water example

Your task is to determine which two bars, when selected to be the sides of the container, form a container that contains the most water. You need to return the area formed by those two bars and the horizontal axis. The width of the bars is considered to be negligible and all bars are spaced one unit apart.

In the example above, you should return the value 42.

container with most water answer

This is because the greatest area occurs when the third and last bars represent the sides of the container. Since the shorter bar has a height of 7 and the two bars are 6 units apart, the area is 7 x 6 = 42.

Suggested Approach

Approach 1 – Using Brute Force

The most intuitive approach to solving this problem is to consider all possible combinations of the two bars.

For instance, we can consider pairing the first bar with the second bar, or the first bar with the third bar, or the first bar with the fourth bar and so on. We can then compare the area formed by each combination and return the greatest area.

However, this approach is too slow and has a time complexity of O(N2).

Approach 2 – Using Pointers

A better approach is to use two pointers to represent the left and right bars of the container. Let’s call the two pointers left and right.

The approach is as follows (we’ll use the list [2, 1, 10, 2, 5, 4, 8, 3, 7] to demonstrate).

Step 1

Start with the greatest possible base for the rectangle. In other words, let left point to the first bar and right to the last bar.

container with most water - left and right pointers

Given that the base of the rectangle is now the greatest, the only way to increase the area of the rectangle is to try to increase its height. We doing that by shifting either the left or right pointer inwards.

Step 2

To shift the pointers, we need to first determine which pointer to shift. To do that, we check the height of the two bars.

Given that the container can only contain water up to the height of the shorter bar (any water above that height will spill out), changing the height of longer bar does not increase the height of the rectangle.

Hence, we’ll shift the pointer of the shorter bar. In the example above, we’ll shift the left pointer.

Step 3

We keep shifting the pointer of the shorter bar until we get an increase in height. That happens at the third bar in the example above.

Step 4

Once we get an increase in height, we calculate the area formed by this new bar and compare it with the current area. If the new area is greater, we update the result.

Step 5

We repeat steps 2 to 4 and keep shifting the left (or right) pointer to try to get a greater area.

Step 6

We keep doing that until the left pointer is no longer smaller than the right pointer.

When that happens, we return the result of the greatest area found.

Graphical Illustration

Here’s a graphical illustration of how the approach works for our example:

container with most water

Suggested Solution

Here’s the suggested solution for the Container with Most Water problem in Python:

def maxArea(height):
    left = 0
    right = len(height) - 1
    result = 0
    currentHeight = 0
    
    while left < right:

        currentHeight = min(height[left], height[right])
        result = max(result, (right - left)*currentHeight)

        if height[left] < height[right]:
            left += 1
            while height[left] < currentHeight:
                left += 1
        else:
            right -= 1
            while height[right] < currentHeight:
                right -= 1

    return result

Here, we first define a function called maxArea() that has one parameter – height.

Next, we declare and initialise two variables – left and right.

left points to the left edge of the container while right points to the right. We initialize left to 0 (i.e. it points to the first bar) and right to len(height) - 1 (i.e. it points to the last bar). This gives the container the greatest possible base.

We also declare two variables called result and currentHeight and initialize them to zero.

Shifting the Pointers

Now, we are ready to shift our left and right pointers.

We do this using the while loop from lines 7 to 19. Inside the loop, we first determine the shorter of the left and right bars and assign its height to currentHeight (on line 9).

Next, we calculate the area of the current rectangle by multiplying currentHeight with the base of the rectangle. We assign the maximum value between the current value of result and newly calculated area to result.

Next, we check if the left bar is shorter than the right bar (on line 12). If it is, the if block on lines 13 to 15 is executed and we increment the value of left by 1. We keep incrementing left by 1 until we get a height that is greater than the current height. This is shown as step 3 in the infographics above.

On the other hand, if the left bar is not shorter than the right bar, the else block on lines 17 to 19 is executed. We keep decrementing the value of right by 1 until we get a height that is greater than the current height. This is shown as step 5 in the infographics above.

Each time we finish updating the value of left (or right), we return to the top of the while loop and check if left is smaller than right (on line 7).

If it is, we execute the while loop again.

We keep executing the while loop until left is no longer smaller than right.

When that happens, result stores the value of the greatest area and we return its value on line 21.

An Example

Let’s look at an example of how this code works.

We’ll use the list [2, 1, 10, 2, 5, 4, 8, 3, 7] to illustrate.

The first time the while loop runs,

left = 0
right = 8

currentHeight 
= min(height[0], height[8])
= min(2, 7)
= 2

result 
= max(result, (right - left)*currentHeight)
= max(0, (8 - 0) * 2)
= max(0, 16)
= 16

The value of result is updated to 16.

After updating the value of result, we check if height[left] is smaller than height[right] (on line 12).

As it is, the if block from lines 13 to 15 is executed and left is updated to 2 (which is the index of the 3rd bar)

After updating the value of left, we return to line 7 to test if left is smaller than right. As it is, we execute the while loop again.

The second time the while loop runs,

left = 2
right = 8

currentHeight
= min(height[2], height[8])
= min(10, 7)
= 7

result
= max(16, (8-2)*7)
= max(16, 42)
= 42

The value of result is updated to 42.

After updating the value of result, we execute the else block (as height[left] is not smaller than height[right]) on lines 17 to 19  to update the value of right to 6.

Since left is smaller than right, we execute the while loop again.

The third time the while loop runs,

left = 2
right = 6

currentHeight
= min(height[2], height[6])
= min(10, 8)
= 8

result
= max(42, (6-2)*8)
= max(42, 32)
= 42

The value of result is NOT updated as 32 is smaller than 42.

We keep updating the values of left (or right) until left is no longer smaller than right.

When that happens, we exit the while loop and return the value of result.

In our example, we return the value 42.

With that, the function is complete.

Written by Jamie | Last Updated November 21, 2020

Recent Posts