medium array/string questions

This commit is contained in:
2025-04-28 22:51:54 +01:00
parent 2014689c5a
commit 9e04184b40
3 changed files with 332 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
title: Container With Most Water
slug: container-with-most-water
difficulty: medium
leetcode_id: 11
leetcode_url: https://leetcode.com/problems/container-with-most-water/
categories:
- arrays
- two-pointers
patterns:
- two-pointers
- 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]).
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.
constraints: |
- 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."
- input: "height = [1,1]"
output: "1"
explanation: "Only container possible has area 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.
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.
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.
common_pitfalls:
- 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]"
- title: Using nested loops
description: |
O(n²) brute force is unnecessary. Two pointers achieve O(n) by eliminating
suboptimal pairs without checking them.
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
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.
solutions:
- approach_name: Two Pointers (Optimal)
is_optimal: true
code: |
def max_area(height: list[int]) -> int:
left, right = 0, len(height) - 1
max_water = 0
while left < right:
width = right - left
h = min(height[left], height[right])
max_water = max(max_water, width * h)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
explanation: |
Start from both ends and move the shorter line inward.
Track maximum area found during traversal.

View File

@@ -0,0 +1,114 @@
title: Longest Substring Without Repeating Characters
slug: longest-substring-without-repeating
difficulty: medium
leetcode_id: 3
leetcode_url: https://leetcode.com/problems/longest-substring-without-repeating-characters/
categories:
- strings
- hash-tables
patterns:
- sliding-window
description: |
Given a string `s`, find the length of the longest substring without repeating characters.
constraints: |
- 0 <= s.length <= 5 * 10^4
- s consists of English letters, digits, symbols and spaces
examples:
- input: 's = "abcabcbb"'
output: "3"
explanation: "The answer is 'abc', with length 3."
- input: 's = "bbbbb"'
output: "1"
explanation: "The answer is 'b', with length 1."
- input: 's = "pwwkew"'
output: "3"
explanation: "The answer is 'wke', with length 3."
explanation:
approach: |
1. Use a sliding window with left and right pointers
2. Maintain a set of characters in the current window
3. Expand right pointer, adding characters to the set
4. When a duplicate is found, shrink from left until duplicate is removed
5. Track maximum window size throughout
intuition: |
We're looking for the longest contiguous substring where all characters are unique.
A sliding window naturally represents a substring.
When we encounter a duplicate, the current window is invalid. Instead of restarting
from scratch, we shrink the window from the left until the duplicate is removed.
This way, we never revisit characters unnecessarily.
common_pitfalls:
- title: Resetting the window incorrectly
description: |
When finding a duplicate, don't start over from the next character.
Instead, shrink from the left until the duplicate is removed.
wrong_approach: "left = right when duplicate found"
correct_approach: "Increment left until duplicate is out of window"
- title: Not handling empty string
description: |
An empty string should return 0. Make sure the algorithm handles this.
- title: Off-by-one in length calculation
description: |
Window length is right - left + 1, or just track max before removing.
key_takeaways:
- Sliding window is ideal for substring problems with constraints
- Use a set or map to track elements in current window
- Shrinking from one end maintains the contiguous property
- This pattern appears in many "longest/shortest with constraint" problems
time_complexity: "O(n)"
space_complexity: "O(min(m, n))"
complexity_explanation: |
Time: Each character is visited at most twice (once by right, once by left).
Space: The set holds at most min(n, m) characters where m is the charset size.
solutions:
- approach_name: Sliding Window with Set (Optimal)
is_optimal: true
code: |
def length_of_longest_substring(s: str) -> int:
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
explanation: |
Expand window by moving right pointer.
When duplicate found, shrink from left until window is valid again.
- approach_name: Optimized with Hash Map
is_optimal: true
code: |
def length_of_longest_substring(s: str) -> int:
char_index = {}
left = 0
max_length = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_length = max(max_length, right - left + 1)
return max_length
explanation: |
Store the last index of each character.
Jump left pointer directly past the duplicate instead of shrinking one by one.

View File

@@ -0,0 +1,119 @@
title: 3Sum
slug: three-sum
difficulty: medium
leetcode_id: 15
leetcode_url: https://leetcode.com/problems/3sum/
categories:
- arrays
- two-pointers
- sorting
patterns:
- two-pointers
description: |
Given an integer array `nums`, return all the triplets [nums[i], nums[j], nums[k]] such that
i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Notice that the solution set must not contain duplicate triplets.
constraints: |
- 3 <= nums.length <= 3000
- -10^5 <= nums[i] <= 10^5
examples:
- input: "nums = [-1,0,1,2,-1,-4]"
output: "[[-1,-1,2],[-1,0,1]]"
explanation: "The distinct triplets that sum to zero."
- input: "nums = [0,1,1]"
output: "[]"
explanation: "No triplet sums to zero."
- input: "nums = [0,0,0]"
output: "[[0,0,0]]"
explanation: "Only one triplet sums to zero."
explanation:
approach: |
1. Sort the array
2. For each element nums[i], find pairs that sum to -nums[i]
3. Use two pointers (left, right) to find pairs in the remaining array
4. Skip duplicates at each level to avoid duplicate triplets
intuition: |
After sorting, for each fixed element nums[i], we need to find nums[j] + nums[k] = -nums[i].
This reduces to the Two Sum II problem on a sorted array, solvable with two pointers.
Sorting enables two things: efficient two-pointer search and easy duplicate skipping.
We skip duplicates by checking if current value equals previous value.
common_pitfalls:
- title: Not handling duplicates
description: |
Without duplicate skipping, you'll return duplicate triplets.
Skip at the outer loop and both pointers.
wrong_approach: "Not skipping when nums[i] == nums[i-1]"
correct_approach: "if i > 0 and nums[i] == nums[i-1]: continue"
- title: Wrong pointer skipping
description: |
After finding a valid triplet, skip duplicates for both left and right pointers
while maintaining left < right.
- title: Starting i too late
description: |
The outer loop should start at index 0. Also, skip if nums[i] > 0 since
sorted array means no valid triplet possible.
key_takeaways:
- Reduce N-sum to (N-1)-sum by fixing one element
- Sorting enables two-pointer approach and duplicate handling
- Duplicate skipping happens at multiple levels
- Time complexity is O(n²) — can't do better for returning all triplets
time_complexity: "O(n²)"
space_complexity: "O(log n) to O(n)"
complexity_explanation: |
Time: O(n log n) for sorting + O(n²) for the two-pointer search.
Space: Depends on sorting algorithm (log n for in-place, n for non-in-place).
solutions:
- approach_name: Sort + Two Pointers (Optimal)
is_optimal: true
code: |
def three_sum(nums: list[int]) -> list[list[int]]:
nums.sort()
result = []
for i in range(len(nums) - 2):
# Skip duplicates for i
if i > 0 and nums[i] == nums[i - 1]:
continue
# Early termination
if nums[i] > 0:
break
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# Skip duplicates for left and right
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return result
explanation: |
Fix one element and use two pointers to find the other two.
Skip duplicates at all levels to avoid duplicate triplets.