feat(content): test cases batch 2
This commit is contained in:
@@ -11,83 +11,126 @@ patterns:
|
||||
- greedy
|
||||
|
||||
description: |
|
||||
You are given an integer array `height` of length n. There are n vertical lines drawn such
|
||||
that the two endpoints of the ith line are (i, 0) and (i, height[i]).
|
||||
You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the i<sup>th</sup> line are `(i, 0)` and `(i, height[i])`.
|
||||
|
||||
Find two lines that together with the x-axis form a container, such that the container
|
||||
contains the most water.
|
||||
Find two lines that together with the x-axis form a container, such that the container contains the most water.
|
||||
|
||||
Return the maximum amount of water a container can store.
|
||||
Return *the maximum amount of water a container can store*.
|
||||
|
||||
**Note:** You may not slant the container.
|
||||
|
||||
constraints: |
|
||||
- n == height.length
|
||||
- 2 <= n <= 10^5
|
||||
- 0 <= height[i] <= 10^4
|
||||
- `n == height.length`
|
||||
- `2 <= n <= 10^5`
|
||||
- `0 <= height[i] <= 10^4`
|
||||
|
||||
examples:
|
||||
- input: "height = [1,8,6,2,5,4,8,3,7]"
|
||||
output: "49"
|
||||
explanation: "Lines at index 1 (height 8) and 8 (height 7) form container with area 7 * 7 = 49."
|
||||
explanation: "Lines at indices 1 (height 8) and 8 (height 7) form a container with area = min(8, 7) × (8 - 1) = 7 × 7 = 49."
|
||||
- input: "height = [1,1]"
|
||||
output: "1"
|
||||
explanation: "Only container possible has area 1 * 1 = 1."
|
||||
explanation: "The only possible container has area = min(1, 1) × 1 = 1."
|
||||
|
||||
explanation:
|
||||
approach: |
|
||||
1. Start with two pointers at the far left and far right
|
||||
2. Calculate the area formed by the two lines
|
||||
3. Move the pointer pointing to the shorter line inward
|
||||
4. Track maximum area seen
|
||||
5. Continue until pointers meet
|
||||
|
||||
intuition: |
|
||||
Area = width × height. The height is limited by the shorter line.
|
||||
Visualise the problem: you have vertical lines of varying heights, and you want to trap the most water between two of them. The water level is limited by the **shorter** line (water would spill over), and the width is the distance between the lines.
|
||||
|
||||
Starting with maximum width (endpoints), we can only improve by finding taller lines.
|
||||
If we move the taller pointer, width decreases and height can't increase beyond the
|
||||
shorter line — so area can only decrease or stay the same.
|
||||
Think of it like this: `Area = width × min(left_height, right_height)`
|
||||
|
||||
Moving the shorter pointer gives us a chance to find a taller line that could
|
||||
increase the height enough to compensate for the reduced width.
|
||||
If we start with pointers at both ends, we have **maximum width**. The only way to potentially increase area is to find taller lines. But here's the key insight:
|
||||
|
||||
If `height[left] < height[right]`, the current area is limited by `height[left]`. Moving `right` inward would:
|
||||
- **Decrease** width (always)
|
||||
- Keep height limited by `height[left]` (at best) or make it smaller
|
||||
|
||||
So moving the **taller** pointer can never improve the area! We should always move the **shorter** pointer, hoping to find a taller line that compensates for the reduced width.
|
||||
|
||||
This greedy choice is provably optimal because we're eliminating pairs that cannot possibly be better than what we've already found.
|
||||
|
||||
approach: |
|
||||
We solve this using **Two Pointers with Greedy Movement**:
|
||||
|
||||
**Step 1: Initialise pointers and tracking variable**
|
||||
|
||||
- `left = 0` (start of array)
|
||||
- `right = len(height) - 1` (end of array)
|
||||
- `max_water = 0` to track the best area found
|
||||
|
||||
|
||||
|
||||
**Step 2: Calculate area and move pointers**
|
||||
|
||||
- While `left < right`:
|
||||
- Calculate current area: `width × min(height[left], height[right])`
|
||||
- Update `max_water` if current area is larger
|
||||
- Move the pointer pointing to the **shorter** line inward:
|
||||
- If `height[left] < height[right]`: increment `left`
|
||||
- Otherwise: decrement `right`
|
||||
|
||||
|
||||
|
||||
**Step 3: Return the maximum**
|
||||
|
||||
- After pointers meet, return `max_water`
|
||||
|
||||
|
||||
|
||||
This works because moving the shorter pointer gives us the only chance to find a better solution. Moving the taller pointer cannot improve the area — it's mathematically impossible.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Moving the wrong pointer
|
||||
- title: Moving the Wrong Pointer
|
||||
description: |
|
||||
Always move the pointer pointing to the shorter line. Moving the taller one
|
||||
cannot possibly increase the area since height is constrained by the shorter.
|
||||
wrong_approach: "Moving left pointer always or randomly"
|
||||
correct_approach: "Move the pointer with smaller height[i]"
|
||||
The algorithm only works if you move the **shorter** pointer. Moving the taller one (or moving randomly) breaks the correctness proof.
|
||||
|
||||
- title: Using nested loops
|
||||
Why? If `height[left] < height[right]`, the area is constrained by `height[left]`. Any container with `left` as one boundary cannot have more water than we've already calculated (width would be smaller, height at most `height[left]`).
|
||||
|
||||
Moving `left` eliminates these inferior possibilities and might find a taller left boundary.
|
||||
wrong_approach: "Always move left pointer, or move randomly"
|
||||
correct_approach: "Always move the pointer with the smaller height"
|
||||
|
||||
- title: Using Nested Loops (Brute Force)
|
||||
description: |
|
||||
O(n²) brute force is unnecessary. Two pointers achieve O(n) by eliminating
|
||||
suboptimal pairs without checking them.
|
||||
Checking all O(n²) pairs works but is far too slow for `n = 10^5` (10 billion operations).
|
||||
|
||||
The two-pointer approach achieves O(n) by intelligently eliminating pairs that cannot be optimal. Each pointer moves at most n times total.
|
||||
wrong_approach: "for i in range(n): for j in range(i+1, n): ..."
|
||||
correct_approach: "Two pointers moving inward based on height comparison"
|
||||
|
||||
- title: Forgetting to Update Maximum
|
||||
description: |
|
||||
Calculate the area **before** moving the pointer, and always update `max_water`. It's easy to forget one of these steps and miss the optimal answer.
|
||||
wrong_approach: "Moving pointer before calculating area"
|
||||
correct_approach: "Calculate area first, update max, then move pointer"
|
||||
|
||||
key_takeaways:
|
||||
- Two pointers from opposite ends is powerful for optimization
|
||||
- Moving the limiting factor gives the best chance of improvement
|
||||
- Greedy choice (move shorter) is provably optimal here
|
||||
- Width × min(heights) is the key formula
|
||||
- "**Two pointers from opposite ends**: A powerful pattern for optimization on sorted/indexed data"
|
||||
- "**Move the limiting factor**: When one element constrains the result, changing it gives the best chance of improvement"
|
||||
- "**Greedy with proof**: This isn't just heuristic — moving the shorter pointer is provably optimal"
|
||||
- "**O(n) vs O(n²)**: Two pointers can eliminate the need for nested loops when the problem has the right structure"
|
||||
|
||||
time_complexity: "O(n)"
|
||||
space_complexity: "O(1)"
|
||||
complexity_explanation: |
|
||||
Time: Each pointer moves at most n times total.
|
||||
Space: Only a few variables for pointers and max area.
|
||||
time_complexity: "O(n). Each pointer moves at most n times, and we do O(1) work per step."
|
||||
space_complexity: "O(1). Only a few variables for pointers and the maximum area."
|
||||
|
||||
solutions:
|
||||
- approach_name: Two Pointers (Optimal)
|
||||
- approach_name: Two Pointers
|
||||
is_optimal: true
|
||||
code: |
|
||||
def max_area(height: list[int]) -> int:
|
||||
left, right = 0, len(height) - 1
|
||||
left = 0
|
||||
right = len(height) - 1
|
||||
max_water = 0
|
||||
|
||||
while left < right:
|
||||
# Calculate width and height of current container
|
||||
width = right - left
|
||||
h = min(height[left], height[right])
|
||||
|
||||
# Update maximum if this container is larger
|
||||
max_water = max(max_water, width * h)
|
||||
|
||||
# Move the pointer pointing to the shorter line
|
||||
# (moving the taller one can't improve the result)
|
||||
if height[left] < height[right]:
|
||||
left += 1
|
||||
else:
|
||||
@@ -95,5 +138,8 @@ solutions:
|
||||
|
||||
return max_water
|
||||
explanation: |
|
||||
Start from both ends and move the shorter line inward.
|
||||
Track maximum area found during traversal.
|
||||
**Time Complexity:** O(n) — Each pointer moves at most n positions total.
|
||||
|
||||
**Space Complexity:** O(1) — Only constant extra space used.
|
||||
|
||||
We start with maximum width and greedily try to find taller lines. By always moving the shorter pointer, we ensure we don't miss any potentially better containers. The proof: any container involving the shorter line at its current position has already been considered (or would have less area due to reduced width).
|
||||
|
||||
Reference in New Issue
Block a user