Back to Two Pointers
Q2Question 2 of 10

Container With Most Water: Opposite-End, Move the Shorter Side

Key Concepts
  • Water is limited by the shorter wall, so moving the shorter pointer inward is the only move that can possibly increase the area.
  • Area = min(h[left], h[right]) * (right - left); O(n) time, O(1) space.
  • Correctness: discarding the shorter line never loses a better answer because all remaining pairs with that line have less width.
  • Equal heights: moving either pointer is correct since both are identically limited.
  • The brute-force all-pairs approach is O(n squared) and unnecessary.
  • This is the canonical example of the greedy opposite-end two-pointer pattern.

Definition

Water is limited by the shorter wall, so moving the shorter pointer inward is the only move that can possibly increase the area.
  • Container With Most Water – find two vertical lines from an array of heights that form the container holding the most water.
  • Area – min(h[left], h[right]) * (right - left); limited by the shorter wall.
  • Greedy rule – always move the pointer at the shorter line inward; moving the taller line cannot improve the area.
  • Correctness – discarding the shorter line is safe because all remaining pairs using it have less width and at most the same height.
  • Complexity – O(n) time, O(1) space.

Think of each bar as the balance on a day, and we want the widest, deepest reserve pool we can form between two days. The water level is capped by the shorter of the two day-balances, and the width is the number of days between them. Rajesh starts with the first and last day, keeps whichever is taller, and moves the shorter day inward hoping to find a taller wall that holds more.

Understanding the Concepts

What is this variant about?

This is the same Container With Most Water problem, emphasising the opposite-end two-pointer approach and the greedy correctness proof. The emphasis here is on proving why the greedy move never misses the optimum, which is the core interview question.

JAVA
package com.bank.dsa;

public class ReservePoolAnalyser {
    public int maxArea(int[] dayBalances) {
        // implementation below
        return 0;
    }
}

How does the opposite-end two-pointer work?

Set left = 0 and right = n - 1. Compute the area as min(h[left], h[right]) * (right - left). Record the maximum. Move the pointer at the shorter line inward. If equal, move either. Repeat until the pointers meet.

JAVA
package com.bank.dsa;

public class OppositeEndContainer {
    public static int maxArea(int[] h) {
        int left = 0, right = h.length - 1;
        int best = 0;
        while (left < right) {
            int area = Math.min(h[left], h[right]) * (right - left);
            best = Math.max(best, area);
            if (h[left] < h[right]) {
                left++;
            } else {
                right--;
            }
        }
        return best;
    }

    public static void main(String[] args) {
        int[] balances = {3, 1, 2, 4, 5, 2, 6};
        System.out.println("Max reserve pool: " + maxArea(balances));
    }
}
TEXT
Max reserve pool: 18

Why is the greedy move correct?

The area is bounded by the shorter line. When we discard the shorter line (move its pointer), every pair that still includes that shorter line as the limiting wall would have a width no larger than the current one (since we started at maximum width). The height is also capped by that same shorter line. So none of those skipped pairs can beat the area we just recorded. This means discarding the shorter line never loses the optimal answer.

TEXT
Proof sketch:
  Let h[left] <= h[right] (left is shorter)
  For any j where left < j < right:
    pair (left, j): width = j - left < right - left
                    height <= h[left]
                    area < h[left] * (right - left) = current area
  So all pairs using left as the limit are dominated.
  Safe to discard left.

What happens when heights are equal?

When h[left] == h[right], moving either pointer is fine. Any pair strictly between them with one of these walls as the limit has strictly less width and at most the same height, so the area cannot improve. Moving both pointers simultaneously is also safe.

JAVA
// All correct for equal heights:
if (h[left] <= h[right]) left++;
if (h[left] < h[right]) left++; else right--;
// Moving both:
if (h[left] == h[right]) { left++; right--; }

How does this compare to brute force?

The brute force tests every pair in O(n squared). The two-pointer approach tests only O(n) pairs because each pointer moves at most n times in total. Each step discards pairs that are provably suboptimal.

JAVA
// Brute force: O(n^2)
for (int i = 0; i < n; i++)
    for (int j = i + 1; j < n; j++)
        best = Math.max(best, Math.min(h[i], h[j]) * (j - i));

// Two pointers: O(n)
while (left < right) { /* compute, move shorter */ }

What about a banking-domain BigDecimal version?

When day balances are BigDecimal amounts, use compareTo for the height comparison and multiply for the area. This keeps the computation exact.

JAVA
package com.bank.dsa;

import java.math.BigDecimal;

public class ExactReservePool {
    public static BigDecimal maxArea(BigDecimal[] h) {
        int left = 0, right = h.length - 1;
        BigDecimal best = BigDecimal.ZERO;
        while (left < right) {
            BigDecimal minH = h[left].min(h[right]);
            BigDecimal area = minH.multiply(BigDecimal.valueOf(right - left));
            if (area.compareTo(best) > 0) best = area;
            if (h[left].compareTo(h[right]) < 0) left++;
            else right--;
        }
        return best;
    }
}

What are edge cases?

Edge cases include arrays with fewer than two elements (return 0), all elements equal (area = height * (n-1), optimal is the two ends), and a strictly descending array (optimal is the two ends).

JAVA
maxArea(new int[]{});            // 0
maxArea(new int[]{5});           // 0
maxArea(new int[]{4, 4, 4, 4}); // 12 (4 * 3)

What are common mistakes?

The most common mistake is computing the area as max(h[left], h[right]) instead of min, which gives the height of the taller wall rather than the limiting shorter wall. Another is moving the taller side instead of the shorter side, which can miss the optimal pair.

JAVA
// Wrong: using max instead of min
int area = Math.max(h[left], h[right]) * (right - left);

// Correct: min is the limiting height
int area = Math.min(h[left], h[right]) * (right - left);

What should a senior engineer know?

A senior engineer states the correctness proof in the interview, not just the mechanics. They explain that discarding the shorter line is safe because all remaining pairs with that line have less width and at most the same height, so none can beat the recorded area. They know this is the canonical greedy opposite-end two-pointer problem.

Visual Overview

DIAGRAM
  balances = [3, 1, 2, 4, 5, 2, 6]
              L                    R

  Step 1: L=0(3), R=6(6)  area = 3*6 = 18
          3 < 6 --> move L
  Step 2: L=1(1), R=6(6)  area = 1*5 = 5
          1 < 6 --> move L
  Step 3: L=2(2), R=6(6)  area = 2*4 = 8
          2 < 6 --> move L
  Step 4: L=3(4), R=6(6)  area = 4*3 = 12
          4 < 6 --> move L
  Step 5: L=4(5), R=6(6)  area = 5*2 = 10
          5 < 6 --> move L
  Step 6: L=5(2), R=6(6)  area = 2*1 = 2
          2 < 6 --> move L
  L >= R --> stop

  Best area = 18 (lines at index 0 and 6)

  Correctness:
  ┌───────────────────────────────────────┐
  │ shorter wall --> limited height       │
  │ moving shorter: only chance to gain   │
  │ moving taller:  can only lose or stay │
  │ discarding shorter: all remaining     │
  │   pairs with it have less width       │
  └───────────────────────────────────────┘

Key Takeaway

Container With Most Water asks me to pick two vertical lines from an array of heights so the container they form holds the most water. I solve it with opposite-end two pointers starting at the first and last lines. At each step I compute the area (shorter height times width), update my running maximum, and move the pointer at the shorter line inward.

The reason I always move the shorter side is the heart of the solution. The area is capped by the shorter line, so moving the taller line inward would only shrink the width while the height stays limited, and the area could never improve. Moving the shorter line is the only move that gives a chance at a taller limiting wall. When both heights are equal, moving either side is fine because both are identically limited.

The subtle guarantee is that discarding the shorter line never loses a better answer, because any other pair that used that short line as its limit would have had less width and at most the same height, so it could not beat the area I already recorded. That is why the greedy pass is correct.

In my banking project at the National Bank of India, I used this for the treasury dashboard to find the liquidity window between two days with the largest sustained reserve. With thousands of days, an all-pairs scan was too slow for an interactive dashboard, and the linear two-pointer approach made it responsive.

Remember one sentence: opposite-end pointers, always move the shorter wall inward, and discarding it is safe because all remaining pairs with it have less width and cannot beat the recorded area.

---

Share
Use ← → arrow keys to navigate