diff --git a/problems/two-pointers-and-sliding-window/container-with-most-water/.test.ts b/problems/two-pointers-and-sliding-window/container-with-most-water/.test.ts new file mode 100644 index 0000000..e5c1adb --- /dev/null +++ b/problems/two-pointers-and-sliding-window/container-with-most-water/.test.ts @@ -0,0 +1,83 @@ +import { test, expect, describe } from 'vitest' +import { nestedLoops, moveSmallerHeight } from './solution' + +const expectCorrectArea = (inputs: [number[], number][]) => { + inputs.forEach(([heights, expected]) => { + expect(nestedLoops(heights)).toBe(expected) + expect(moveSmallerHeight(heights)).toBe(expected) + }) +} + +describe('Positive area is returned by both solutions correctly when:', () => { + test('Max area is between the two tallest lines.', () => { + const inputs = [ + [[1,8,6,2,5,4,8,3,7], 49], + [[1,2,100,100,2,1], 100], + [[1,3,51,49,50,30], 100], + [[100,1,1,1,2,90], 450] + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) + test('Max area is between the two shortest lines.', () => { + const inputs = [ + [[6, 6, 6, 8, 8, 6, 5], 30], + [[3,4,4,4,3], 12], + [[9, 10, 11, 12, 1, 1, 8], 48] + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) + test('Max area is between the tallest line and the shortest line.', () => { + const inputs = [ + [[100, 3, 1], 3], + [[100, 1, 1], 2], + [[1, 2, 3], 2], + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) + test('Max area is between two lines that are not the tallest or shortest.', () => { + const inputs = [ + [[1, 3, 4, 4, 3, 1], 9], + [[1, 2, 3, 4, 5], 6], + [[5, 4, 3, 2, 1], 6], + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) + test('All heights are the same.', () => { + const inputs = [ + [[1, 1, 1, 1], 3], + [[2, 2, 2, 2], 6], + [[3, 3, 3, 3], 9], + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) +}) + +describe('Both solutions return the minimum height when:', () => { + test('Input array has length 2.', () => { + const inputs = [ + [[1, 1], 1], + [[2, 3], 2], + [[6, 4], 4], + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) +}) + +describe('Both solutions return zero area when:', () => { + test('All heights are zero.', () => { + const inputs = [ + [[0, 0, 0, 0], 0], + [[0, 0, 0, 0, 0], 0], + [[0, 0], 0], + ] satisfies [number[], number][] + expectCorrectArea(inputs) + }) +}) + +describe('Optimized solution runs for large inputs.', () => { + test('Input array has 10^5 elements.', () => { + // Set array length to maximum allowed by problem constraints + const heights = new Array(10**5).fill(1) + expect(moveSmallerHeight(heights)).toBe(99999) + }) +}) diff --git a/problems/two-pointers-and-sliding-window/container-with-most-water/explanation.md b/problems/two-pointers-and-sliding-window/container-with-most-water/explanation.md new file mode 100644 index 0000000..13acd21 --- /dev/null +++ b/problems/two-pointers-and-sliding-window/container-with-most-water/explanation.md @@ -0,0 +1,130 @@ +# Container With Most Water +🟠 Medium + +Patterns: Nested Loops, Bidirectional Pointers. +## Problem +For a given array ```height``` consisting of integers, suppose for each index ```i```, a vertical line of height ```height[i]``` is drawn from the x-axis. For any two lines, a container can be formed between them and the x-axis. Find the two lines that maximize the amount of water stored in this container. + +**Note:** The container cannot be slanted. + +## Constraints +- ```2 <= n <= 105``` +- ```0 <= height[i] <= 104``` +## Examples +``` +height = [1,8,6,2,5,4,8,3,7] +output = 49 +``` +![Example](/documentation/images/cwmw.png) + +The container that holds the most water is between lines with indices 0 and 8. Since the container cannot be slanted, the area of the water is $7 \times 7 = 49$. +``` +height = [2,3,4,5,18,17,6] +output = 17 +``` + + +The maximum area is $17$ which is found between lines with indices 4 and 5. Note that although this is the smallest width possible, the chosen lines are sufficiently higher than the others to outweigh this. +## Intuition +This problem is essentially asking 'Find the two indices that maximize width x height, where width is the difference of the indices, and height is the minimum value in the array at the two indices. As with most array optimization problems, an obvious but inefficient solution is to simply check all pairs of indices, and keep track of the largest area found. A strategy to reduce the algorithm's complexity can be found by making note of a key insight: For any given pair of lines, moving the taller line inward cannot increase the area. This is because the width decreases, while the height remains limited by the shorter line. + +Therefore, to potentially find a larger area, we must move the pointer at the smaller height. This leads to a two-pointer strategy where both pointers start at the ends of the array and converge inward. + +## Brute Force Approach +The brute force solution checks all pairs of lines and computes the area for each pair. The maximum area is updated whenever a larger value is found. + +```javascript +for (let i=0; i { + const currWidth = right - left + const currHeight = Math.min(height[left], height[right]) + return currWidth * currHeight +} + +// Solution 1: Brute force nested loops +// Time O(n^2), Space O(1) +// Solution 2: Two pointers moving smaller height +// Time O(n), Space O(1) + +/** + * Brute force nested loops + * Time O(n^2), Space O(1) + */ +export function nestedLoops(height: number[]): number { + let maxArea = 0 + + const n = height.length + + for (let i=0; i