How do you solve Container With Most Water, and why is moving the shorter wall the right move?
Start one pointer at each end of the array and move whichever points at the shorter wall inwards. At every step, compute the area as the shorter wall times the width between the pointers and keep the best.
def max_area(height):
left, right = 0, len(height) - 1
best = 0
while left < right:
area = min(height[left], height[right]) * (right - left)
if area > best:
best = area
if height[left] < height[right]:
left += 1
else:
right -= 1
return best
print(max_area([1, 8, 6, 2, 5, 4, 8, 3, 7]))
print(max_area([1, 1]))The outputs are 49 and 1. For the first array the best container uses the wall of height eight at index one and the wall of height seven at index eight, giving seven times eight, which is 49. The second array can only hold one unit of water because both walls are one high.
The greedy argument is the heart of the solution. Suppose the left wall is shorter than the right wall. Any container that uses the left wall and a different right wall is narrower, so its area is at most the left height times a smaller width, which is strictly less than what the current pair already achieves. Therefore the left wall can never be part of a better answer, and discarding it is safe. The mirror argument applies when the right wall is shorter.
The trade-off to state is that this beats the brute force scan, which tries every pair of walls and costs O of n squared, by exploiting the monotonic relationship between width and height. The two pointers version is O of n time and O of 1 space. The only caveat is that it is a one-pass greedy argument: it relies on the area being the product of the shorter wall and the width, and that relation is what makes discarding the shorter wall safe.