hard questions
This commit is contained in:
122
backend/data/questions/median-of-two-sorted-arrays.yaml
Normal file
122
backend/data/questions/median-of-two-sorted-arrays.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
title: Median of Two Sorted Arrays
|
||||
slug: median-of-two-sorted-arrays
|
||||
difficulty: hard
|
||||
leetcode_id: 4
|
||||
leetcode_url: https://leetcode.com/problems/median-of-two-sorted-arrays/
|
||||
categories:
|
||||
- arrays
|
||||
- binary-search
|
||||
patterns:
|
||||
- binary-search
|
||||
|
||||
description: |
|
||||
Given two sorted arrays `nums1` and `nums2` of size m and n respectively, return the median
|
||||
of the two sorted arrays.
|
||||
|
||||
The overall run time complexity should be O(log(m+n)).
|
||||
|
||||
constraints: |
|
||||
- nums1.length == m
|
||||
- nums2.length == n
|
||||
- 0 <= m <= 1000
|
||||
- 0 <= n <= 1000
|
||||
- 1 <= m + n <= 2000
|
||||
- -10^6 <= nums1[i], nums2[i] <= 10^6
|
||||
|
||||
examples:
|
||||
- input: "nums1 = [1,3], nums2 = [2]"
|
||||
output: "2.0"
|
||||
explanation: "Merged array is [1,2,3]. Median is 2."
|
||||
- input: "nums1 = [1,2], nums2 = [3,4]"
|
||||
output: "2.5"
|
||||
explanation: "Merged array is [1,2,3,4]. Median is (2+3)/2 = 2.5."
|
||||
|
||||
explanation:
|
||||
approach: |
|
||||
1. Binary search on the smaller array for partition point
|
||||
2. Partition both arrays such that left half has (m+n+1)//2 elements
|
||||
3. Check if partition is valid: max(left) <= min(right)
|
||||
4. If valid, compute median from boundary elements
|
||||
5. Adjust binary search bounds based on comparison
|
||||
|
||||
intuition: |
|
||||
The median divides the combined array into two halves of equal size. We don't need to
|
||||
actually merge; we just need to find the correct partition.
|
||||
|
||||
If we choose i elements from nums1 for the left half, we need (m+n+1)//2 - i from nums2.
|
||||
Binary search on i (0 to m) to find where nums1[i-1] <= nums2[j] and nums2[j-1] <= nums1[i].
|
||||
|
||||
This is O(log min(m,n)) since we binary search on the smaller array.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Not handling edge cases at partition
|
||||
description: |
|
||||
When partition is at array boundary (i=0 or i=m), use -inf or inf for boundary values.
|
||||
wrong_approach: "Accessing nums1[i-1] when i=0"
|
||||
correct_approach: "Use float('-inf') if i == 0"
|
||||
|
||||
- title: Binary searching on the longer array
|
||||
description: |
|
||||
Always binary search on the shorter array to ensure valid partition exists
|
||||
and for better efficiency.
|
||||
|
||||
- title: Odd vs even total length
|
||||
description: |
|
||||
For odd total, median is max of left half.
|
||||
For even, it's average of max(left) and min(right).
|
||||
|
||||
key_takeaways:
|
||||
- Binary search on partition, not on values
|
||||
- Partition both arrays to have equal halves
|
||||
- Handle boundary conditions with infinity
|
||||
- O(log min(m,n)) is achievable
|
||||
|
||||
time_complexity: "O(log min(m,n))"
|
||||
space_complexity: "O(1)"
|
||||
complexity_explanation: |
|
||||
Time: Binary search on the smaller array.
|
||||
Space: Only constant extra variables.
|
||||
|
||||
solutions:
|
||||
- approach_name: Binary Search on Partition (Optimal)
|
||||
is_optimal: true
|
||||
code: |
|
||||
def find_median_sorted_arrays(nums1: list[int], nums2: list[int]) -> float:
|
||||
# Ensure nums1 is the smaller array
|
||||
if len(nums1) > len(nums2):
|
||||
nums1, nums2 = nums2, nums1
|
||||
|
||||
m, n = len(nums1), len(nums2)
|
||||
left, right = 0, m
|
||||
half_len = (m + n + 1) // 2
|
||||
|
||||
while left <= right:
|
||||
i = (left + right) // 2 # Partition in nums1
|
||||
j = half_len - i # Partition in nums2
|
||||
|
||||
# Handle edge cases with infinity
|
||||
nums1_left = float('-inf') if i == 0 else nums1[i - 1]
|
||||
nums1_right = float('inf') if i == m else nums1[i]
|
||||
nums2_left = float('-inf') if j == 0 else nums2[j - 1]
|
||||
nums2_right = float('inf') if j == n else nums2[j]
|
||||
|
||||
if nums1_left <= nums2_right and nums2_left <= nums1_right:
|
||||
# Found valid partition
|
||||
if (m + n) % 2 == 1:
|
||||
return max(nums1_left, nums2_left)
|
||||
else:
|
||||
return (max(nums1_left, nums2_left) +
|
||||
min(nums1_right, nums2_right)) / 2
|
||||
|
||||
elif nums1_left > nums2_right:
|
||||
# Too many elements from nums1 in left half
|
||||
right = i - 1
|
||||
else:
|
||||
# Too few elements from nums1 in left half
|
||||
left = i + 1
|
||||
|
||||
return 0.0 # Should never reach here
|
||||
explanation: |
|
||||
Binary search to find correct partition point in the smaller array.
|
||||
Partition is valid when all left elements <= all right elements.
|
||||
Compute median from the four boundary elements.
|
||||
149
backend/data/questions/merge-k-sorted-lists.yaml
Normal file
149
backend/data/questions/merge-k-sorted-lists.yaml
Normal file
@@ -0,0 +1,149 @@
|
||||
title: Merge k Sorted Lists
|
||||
slug: merge-k-sorted-lists
|
||||
difficulty: hard
|
||||
leetcode_id: 23
|
||||
leetcode_url: https://leetcode.com/problems/merge-k-sorted-lists/
|
||||
categories:
|
||||
- linked-lists
|
||||
- heap
|
||||
patterns:
|
||||
- heap
|
||||
|
||||
description: |
|
||||
You are given an array of k linked-lists `lists`, each linked-list is sorted in ascending order.
|
||||
|
||||
Merge all the linked-lists into one sorted linked-list and return it.
|
||||
|
||||
constraints: |
|
||||
- k == lists.length
|
||||
- 0 <= k <= 10^4
|
||||
- 0 <= lists[i].length <= 500
|
||||
- -10^4 <= lists[i][j] <= 10^4
|
||||
- lists[i] is sorted in ascending order
|
||||
- The sum of lists[i].length will not exceed 10^4
|
||||
|
||||
examples:
|
||||
- input: "lists = [[1,4,5],[1,3,4],[2,6]]"
|
||||
output: "[1,1,2,3,4,4,5,6]"
|
||||
explanation: "Merge three sorted lists into one."
|
||||
- input: "lists = []"
|
||||
output: "[]"
|
||||
explanation: "No lists to merge."
|
||||
- input: "lists = [[]]"
|
||||
output: "[]"
|
||||
explanation: "Single empty list."
|
||||
|
||||
explanation:
|
||||
approach: |
|
||||
1. Use a min-heap to track the smallest element among all list heads
|
||||
2. Add the first node from each non-empty list to the heap
|
||||
3. Pop the smallest node, add it to the result
|
||||
4. If that node has a next, add it to the heap
|
||||
5. Continue until heap is empty
|
||||
|
||||
intuition: |
|
||||
At each step, we need to find the minimum among k candidates (the heads of each list).
|
||||
A min-heap gives us this minimum in O(log k) time.
|
||||
|
||||
Since each node is pushed and popped from the heap exactly once, and we have N total nodes,
|
||||
the overall complexity is O(N log k).
|
||||
|
||||
common_pitfalls:
|
||||
- title: Not handling empty lists
|
||||
description: |
|
||||
Some lists might be empty (null). Filter them out or check before adding to heap.
|
||||
wrong_approach: "Adding null to heap"
|
||||
correct_approach: "if node: heappush(...)"
|
||||
|
||||
- title: Heap comparison with ListNode
|
||||
description: |
|
||||
Python's heapq can't compare ListNode objects directly.
|
||||
Either use a tuple (value, index, node) or define __lt__ on ListNode.
|
||||
|
||||
- title: Not advancing the list pointer
|
||||
description: |
|
||||
After adding a node to result, push its next node to the heap, not the same node.
|
||||
|
||||
key_takeaways:
|
||||
- Min-heap efficiently finds minimum among k elements
|
||||
- This is a k-way merge algorithm
|
||||
- Total work is O(N log k) where N is total nodes
|
||||
- Same pattern works for merging k sorted arrays
|
||||
|
||||
time_complexity: "O(N log k)"
|
||||
space_complexity: "O(k)"
|
||||
complexity_explanation: |
|
||||
Time: Each of N nodes is pushed and popped once, each operation is O(log k).
|
||||
Space: Heap holds at most k nodes at any time.
|
||||
|
||||
solutions:
|
||||
- approach_name: Min-Heap (Optimal)
|
||||
is_optimal: true
|
||||
code: |
|
||||
import heapq
|
||||
|
||||
class ListNode:
|
||||
def __init__(self, val=0, next=None):
|
||||
self.val = val
|
||||
self.next = next
|
||||
|
||||
def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None:
|
||||
heap = []
|
||||
|
||||
# Add first node from each list with index for tie-breaking
|
||||
for i, node in enumerate(lists):
|
||||
if node:
|
||||
heapq.heappush(heap, (node.val, i, node))
|
||||
|
||||
dummy = ListNode()
|
||||
current = dummy
|
||||
|
||||
while heap:
|
||||
val, i, node = heapq.heappop(heap)
|
||||
current.next = node
|
||||
current = current.next
|
||||
|
||||
if node.next:
|
||||
heapq.heappush(heap, (node.next.val, i, node.next))
|
||||
|
||||
return dummy.next
|
||||
explanation: |
|
||||
Use heap to always get the smallest current head.
|
||||
Push next node when popping to maintain k candidates.
|
||||
Index in tuple handles equal values (tie-breaking).
|
||||
|
||||
- approach_name: Divide and Conquer
|
||||
is_optimal: true
|
||||
code: |
|
||||
def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None:
|
||||
if not lists:
|
||||
return None
|
||||
|
||||
def merge_two(l1: ListNode | None, l2: ListNode | None) -> ListNode | None:
|
||||
dummy = ListNode()
|
||||
current = dummy
|
||||
|
||||
while l1 and l2:
|
||||
if l1.val <= l2.val:
|
||||
current.next = l1
|
||||
l1 = l1.next
|
||||
else:
|
||||
current.next = l2
|
||||
l2 = l2.next
|
||||
current = current.next
|
||||
|
||||
current.next = l1 or l2
|
||||
return dummy.next
|
||||
|
||||
while len(lists) > 1:
|
||||
merged = []
|
||||
for i in range(0, len(lists), 2):
|
||||
l1 = lists[i]
|
||||
l2 = lists[i + 1] if i + 1 < len(lists) else None
|
||||
merged.append(merge_two(l1, l2))
|
||||
lists = merged
|
||||
|
||||
return lists[0]
|
||||
explanation: |
|
||||
Pair up lists and merge, reducing k to k/2 each round.
|
||||
Same complexity as heap approach but iterative merge logic.
|
||||
129
backend/data/questions/trapping-rain-water.yaml
Normal file
129
backend/data/questions/trapping-rain-water.yaml
Normal file
@@ -0,0 +1,129 @@
|
||||
title: Trapping Rain Water
|
||||
slug: trapping-rain-water
|
||||
difficulty: hard
|
||||
leetcode_id: 42
|
||||
leetcode_url: https://leetcode.com/problems/trapping-rain-water/
|
||||
categories:
|
||||
- arrays
|
||||
- two-pointers
|
||||
- stack
|
||||
patterns:
|
||||
- two-pointers
|
||||
- monotonic-stack
|
||||
|
||||
description: |
|
||||
Given n non-negative integers representing an elevation map where the width of each bar is 1,
|
||||
compute how much water it can trap after raining.
|
||||
|
||||
constraints: |
|
||||
- n == height.length
|
||||
- 1 <= n <= 2 * 10^4
|
||||
- 0 <= height[i] <= 10^5
|
||||
|
||||
examples:
|
||||
- input: "height = [0,1,0,2,1,0,1,3,2,1,2,1]"
|
||||
output: "6"
|
||||
explanation: "6 units of water are trapped between the bars."
|
||||
- input: "height = [4,2,0,3,2,5]"
|
||||
output: "9"
|
||||
explanation: "9 units of water are trapped."
|
||||
|
||||
explanation:
|
||||
approach: |
|
||||
1. Use two pointers from left and right
|
||||
2. Track maximum height seen from each side
|
||||
3. Move the pointer with smaller max height
|
||||
4. Water at current position = max_height - current_height
|
||||
5. Add to total and continue until pointers meet
|
||||
|
||||
intuition: |
|
||||
Water at any position is determined by the minimum of the maximum heights to its left
|
||||
and right, minus the current height.
|
||||
|
||||
With two pointers, we track left_max and right_max. If left_max < right_max, water at
|
||||
the left pointer is limited by left_max (the right side is guaranteed to be at least
|
||||
as tall). We process and move the pointer with the smaller maximum.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Only considering one side
|
||||
description: |
|
||||
Water level is determined by BOTH sides. You need to track maximum from left AND right.
|
||||
wrong_approach: "Only tracking left_max"
|
||||
correct_approach: "Track both left_max and right_max"
|
||||
|
||||
- title: Counting bars instead of water
|
||||
description: |
|
||||
Water trapped at position i is max_height - height[i], not max_height.
|
||||
The bar itself takes up space.
|
||||
|
||||
- title: Not updating max heights
|
||||
description: |
|
||||
Update left_max or right_max before calculating water, not after.
|
||||
|
||||
key_takeaways:
|
||||
- Two pointers eliminate need for O(n) precomputation
|
||||
- Water level = min(left_max, right_max) - current_height
|
||||
- Always process the side with smaller max (guaranteed bound)
|
||||
- This can also be solved with monotonic stack or DP
|
||||
|
||||
time_complexity: "O(n)"
|
||||
space_complexity: "O(1)"
|
||||
complexity_explanation: |
|
||||
Time: Single pass with two pointers.
|
||||
Space: Only a few variables for pointers and max values.
|
||||
|
||||
solutions:
|
||||
- approach_name: Two Pointers (Optimal)
|
||||
is_optimal: true
|
||||
code: |
|
||||
def trap(height: list[int]) -> int:
|
||||
if not height:
|
||||
return 0
|
||||
|
||||
left, right = 0, len(height) - 1
|
||||
left_max, right_max = 0, 0
|
||||
water = 0
|
||||
|
||||
while left < right:
|
||||
if height[left] < height[right]:
|
||||
if height[left] >= left_max:
|
||||
left_max = height[left]
|
||||
else:
|
||||
water += left_max - height[left]
|
||||
left += 1
|
||||
else:
|
||||
if height[right] >= right_max:
|
||||
right_max = height[right]
|
||||
else:
|
||||
water += right_max - height[right]
|
||||
right -= 1
|
||||
|
||||
return water
|
||||
explanation: |
|
||||
Process from both ends. Move the pointer with smaller max height.
|
||||
Add water based on the difference between max height and current height.
|
||||
|
||||
- approach_name: Monotonic Stack
|
||||
is_optimal: false
|
||||
code: |
|
||||
def trap(height: list[int]) -> int:
|
||||
stack = [] # stores indices
|
||||
water = 0
|
||||
|
||||
for i, h in enumerate(height):
|
||||
while stack and h > height[stack[-1]]:
|
||||
top = stack.pop()
|
||||
|
||||
if not stack:
|
||||
break
|
||||
|
||||
width = i - stack[-1] - 1
|
||||
bounded_height = min(h, height[stack[-1]]) - height[top]
|
||||
water += width * bounded_height
|
||||
|
||||
stack.append(i)
|
||||
|
||||
return water
|
||||
explanation: |
|
||||
Stack stores indices of bars in decreasing height order.
|
||||
When a taller bar is found, calculate water trapped in the "valley".
|
||||
Reference in New Issue
Block a user