Start learning free

LESSON 256 OF 414 · TWO POINTERS

Container With Most Water in Scala: O(n) with Two Pointers

Practice O(n) Read the full explanation

You are given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i are (i, ai) and (i, 0). Find two lines, which together with the x-axis form a container, such that this container contains the most water.

Note: You may not slant the container and n is at least 2.

 

The above vertical lines are represented by the array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) one container can contain is 49.

 

Example:

Input: [1,8,6,2,5,4,8,3,7]
Output: 49
STEP 1 OF 7
    Ctrl + Enter to check

    SCALA · TWO POINTERS

    Container With Most Water in Scala, explained

    Problem overview

    The method maxArea(height: Array[Int]) receives non-negative integers. Entry i is a vertical line at position i whose height is height(i). Any two lines, together with the x-axis, form a container. The method returns the largest amount of water such a container can hold. The container can't be tilted, and there are at least two lines.

    The water level is limited by the shorter of the two lines, and the width is the distance between them, so the area of a container is min(height(left), height(right)) * (right - left).

    For [1, 8, 6, 2, 5, 4, 8, 3, 7] the best pair is the 8 at index 1 and the 7 at index 8. The shorter line is 7 and the distance is 7, so the area is 49.

    Approach: start wide, move the shorter line

    Trying every pair of lines is correct but takes O(n²). The two-pointer method needs one pass.

    Start with the widest container: left at the first line and right at the last. Measure its area and keep the best so far. Now one pointer has to move inward, which makes the container narrower. The question is which one.

    Moving the taller line can't help. The new container is narrower and its height is still capped by the shorter line, which hasn't changed, so its area is no larger than before. Moving the shorter line is the only move that can make the area grow, because the next line might be tall enough to raise the cap. So the pointer at the shorter line moves. Every container that was skipped has been ruled out as no better than the one just measured.

    When the pointers meet there are no pairs left, and the best area seen is the answer.

    On the example, the first container uses heights 1 and 7: area 1 * 8 = 8. The left line is shorter, so left moves. Next the pair is 8 and 7 with width 7: area 49. The right line is the shorter one, so right moves, and the remaining pairs never beat 49.

    Step by step

    1. Declare three variables. result starts at 0, left at 0 and right at height.length - 1. The solution puts them on one line, separated by semicolons.
    2. Loop while left < right. A container needs two different lines.
    3. Compute the area as the smaller of the two heights, math.min(height(left), height(right)), multiplied by the distance right - left.
    4. Keep the best: result = math.max(result, area).
    5. If height(left) < height(right), do left += 1: the left line is the limit, so it is the one to replace.
    6. Otherwise, do right -= 1. When the heights are equal, moving either side is safe, and this branch moves right.
    7. Return result.

    The solution in Scala

    class Solution {
        def maxArea(height: Array[Int]): Int = {
            var result = 0; var left = 0; var right = height.length - 1
            while (left < right) {
                val area = math.min(height(left), height(right)) * (right - left)
                result = math.max(result, area)
                if (height(left) < height(right)) {
                    left += 1
                } else {
                    right -= 1
                }
            }
            return result
        }
    }
    

    The Scala details worth knowing:

    • Semicolons. Scala ends a statement at the end of the line, so semicolons are optional. They are only needed to put several statements on one line, as in the first line of the body.
    • math.min and math.max. math is Scala's own package and needs no import. Both take two numbers and return one.
    • val inside the loop. area is created fresh on every iteration and never changes, so val fits. The values that do change, result, left and right, are vars.
    • Parentheses for indexing. It is height(left), not height[left].
    • Int is 32 bits. An area above 2147483647 would wrap silently, which only matters for very large heights on very long arrays.

    Complexity

    Time: O(n). Each iteration moves one pointer inward, so the loop runs at most n - 1 times, and each iteration does constant work.

    Space: O(1). The method uses a handful of integers.

    Edge cases and common mistakes

    • Two lines. [1, 1] gives 1: height 1, width 1.
    • Zero heights. [0, 0] gives 0, since water can't rise above a line of height 0.
    • Equal heights. [5, 5, 5, 5] gives 15, from the two outer lines.
    • A tall pair far apart. [4, 3, 2, 1, 4] gives 16, from the two 4s. The height of the lines in between doesn't matter.
    • Using the maximum instead of the minimum. Water above the shorter line would spill, so the height of a container is the smaller of the two lines.
    • Moving the taller line. That can only shrink the width without raising the cap, and it can discard the best pair.
    • Forgetting the width. The area is height times distance, not just height.
    • Fewer than two lines. The problem guarantees at least two. For an array with 0 or 1 entries the loop never runs, and the method returns 0.

    Testing

    • [1, 8, 6, 2, 5, 4, 8, 3, 7] gives 49.
    • [1, 1] gives 1.
    • [4, 3, 2, 1, 4] gives 16.
    • [1, 2, 1] gives 2.
    • [2, 3, 4, 5, 18, 17, 6] gives 17.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free