LeetCode 757 - Set Intersection Size At Least Two
The problem asks us to find the minimum size of a set of integers, nums, such that each given interval [starti, endi] contains at least two integers from nums.
Difficulty: 🔴 Hard
Topics: Array, Greedy, Sorting
Solution
Problem Understanding
The problem asks us to find the minimum size of a set of integers, nums, such that each given interval [starti, endi] contains at least two integers from nums. Each interval is represented as a closed interval, meaning all integers between starti and endi inclusive belong to that interval. The goal is to construct nums such that this condition is satisfied for all intervals, but with the smallest possible number of elements.
The input is a 2D array intervals, where 1 <= intervals.length <= 3000 and each interval has 0 <= starti < endi <= 10^8. These constraints imply that a naive approach that enumerates all possible sets is infeasible due to the high potential range of integers and the number of intervals. Edge cases include intervals that overlap completely, intervals that barely touch, and intervals that are minimal in length (size 2, which requires both numbers to be in the set).
The output is a single integer representing the minimum number of integers required in nums to satisfy the "at least two integers per interval" condition.
Approaches
A brute-force approach would attempt to try all possible combinations of integers within the union of all intervals, checking which combination satisfies the condition of at least two integers per interval. While this guarantees correctness, the number of possible sets grows combinatorially with the size of the intervals, making it computationally infeasible given the constraints.
The key insight for an optimal solution is to use a greedy approach with sorting. We can sort the intervals primarily by their end points in ascending order and secondarily by their start points in descending order for intervals with the same end. This ensures that we process intervals from left to right in a way that maximizes reuse of previously selected numbers. We always select the last two largest numbers in each interval if the interval is not already satisfied by existing numbers. By carefully tracking the two largest numbers included from previous intervals, we minimize additional selections.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential | O(n) | Generate all sets of integers in the intervals and check coverage |
| Optimal (Greedy + Sorting) | O(n log n) | O(1) | Sort intervals and track last two elements added, extend the set greedily |
Algorithm Walkthrough
- Sort intervals: Sort the intervals by their end in ascending order. If two intervals have the same end, sort by start in descending order. This ensures that intervals that end earlier are handled first, maximizing the chance that previously added numbers satisfy future intervals.
- Initialize two trackers: Maintain two variables,
firstandsecond, to track the two largest numbers currently in the containing set. Initialize them to-1and-1since no numbers are included yet. - Iterate over intervals: For each interval
[start, end], check how many of the current two largest numbers fall within this interval. - Decide numbers to add:
- If both
firstandsecondare in the interval, do nothing. - If only
secondis in the interval, addendto the set and updatefirstandsecond. - If neither are in the interval, add
end-1andendto the set and updatefirstandsecond.
- Return set size: Keep a running count of the total numbers added to
nums. At the end, this count is the minimum possible size of the containing set.
Why it works: By always selecting the largest numbers within the interval (greedily), we maximize coverage for future intervals because intervals are sorted by end. This guarantees that the algorithm adds the minimum necessary numbers while maintaining the invariant that every processed interval has at least two numbers.
Python Solution
from typing import List
class Solution:
def intersectionSizeTwo(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: (x[1], -x[0]))
first, second = -1, -1
count = 0
for start, end in intervals:
if start <= first:
continue
elif start <= second:
first = second
second = end
count += 1
else:
first = end - 1
second = end
count += 2
return count
This implementation first sorts the intervals and initializes trackers for the last two numbers added. It iterates through each interval, checking coverage with the current first and second values. Depending on the overlap, it decides how many new numbers to add. This ensures minimal additions while satisfying the "at least two per interval" condition.
Go Solution
import "sort"
func intersectionSizeTwo(intervals [][]int) int {
sort.Slice(intervals, func(i, j int) bool {
if intervals[i][1] == intervals[j][1] {
return intervals[i][0] > intervals[j][0]
}
return intervals[i][1] < intervals[j][1]
})
first, second := -1, -1
count := 0
for _, interval := range intervals {
start, end := interval[0], interval[1]
if start <= first {
continue
} else if start <= second {
first = second
second = end
count++
} else {
first = end - 1
second = end
count += 2
}
}
return count
}
The Go solution mirrors the Python implementation. Sorting uses sort.Slice with a custom comparator. The main difference is Go syntax, such as the for loop over slices and tuple unpacking replacement.
Worked Examples
Example 1: [[1,3],[3,7],[8,9]]
- Sort →
[[1,3],[3,7],[8,9]] - Initialize
first=-1, second=-1, count=0 - Interval
[1,3]→ neither first nor second inside → add2,3, updatefirst=2, second=3,count=2 - Interval
[3,7]→ onlysecond=3inside → add7, updatefirst=3, second=7,count=3 - Interval
[8,9]→ neither inside → add8,9, updatefirst=8, second=9,count=5
Final result: 5
Example 2: [[1,3],[1,4],[2,5],[3,5]]
- Sorted:
[[1,3],[1,4],[2,5],[3,5]] - Interval
[1,3]→ add2,3, count=2 - Interval
[1,4]→ both numbers in interval? 2,3 → yes, nothing to add - Interval
[2,5]→ 2,3 in interval → yes, nothing to add - Interval
[3,5]→ 2,3 → only second in interval? yes → add5, count=3
Result: 3
Example 3: [[1,2],[2,3],[2,4],[4,5]]
- Sorted:
[[1,2],[2,3],[2,4],[4,5]] [1,2]→ add1,2, count=2[2,3]→ only second=2 inside → add 3, count=3[2,4]→ only second=3 inside → add 4, count=4[4,5]→ only second=4 inside → add 5, count=5
Result: 5
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting dominates, iterating through intervals is O(n) |
| Space | O(1) | Only a constant number of variables are used aside from input |
Sorting ensures intervals are processed optimally, and tracking only the last two numbers keeps memory usage minimal.
Test Cases
# Provided examples
assert Solution().intersectionSizeTwo([[1,3],[3,7],[8,9]]) == 5 # basic intervals
assert Solution().intersectionSizeTwo([[1,3],[1,4],[2,5],[3,5]]) == 3 # overlapping intervals
assert Solution().intersectionSizeTwo([[1,2],[2,3],[2,4],[4,5]]) == 5 # consecutive intervals
# Edge cases
assert Solution().intersectionSizeTwo([[0,1]]) == 2 # minimal interval, must include both
assert Solution().intersectionSizeTwo([[0,10**8]]) == 2 # very large interval, still only need last two
assert Solution().intersectionSizeTwo([[1,3],[2,5],[4,6]]) == 4 # overlapping, need careful selection
assert Solution().intersectionSizeTwo([[1,2],[3,4],[5,6]]) == 6 # non-overlapping, must add all
| Test | Why |
|