You are given a list of intervals, where each interval is represented as a pair [start, end]
and includes every integer between start
and end
(inclusive). Your task is to select the smallest possible set of integers such that every interval contains at least two of the selected integers.
Key constraints:
intervals
.Example: For intervals = [[1,3], [1,4], [2,5], [3,5]]
, one possible answer is 3
(with the set {2, 3, 4}).
The brute-force way would be to try every possible subset of integers and see whether it covers all intervals with at least two elements each. However, this approach is computationally infeasible for larger inputs.
Instead, we want to be greedy: for each interval, we want to place our chosen numbers as "late" as possible, so that they can cover future intervals as well. By sorting the intervals and always picking the largest possible numbers within each interval, we maximize reuse.
The key insight is that, by processing intervals in a certain order and always ensuring the "latest" numbers are chosen, we avoid unnecessary duplication and minimize the total count.
Let's break the algorithm into steps:
This approach ensures that each interval is satisfied with at least two elements, and that the chosen elements are reused as much as possible.
Let's walk through the example intervals = [[1,3], [1,4], [2,5], [3,5]]
.
[[1,3], [1,4], [2,5], [3,5]]
(already sorted in this case).
Brute-force: Checking all subsets of possible integers is exponential, roughly O(2^N), and not feasible for large N.
Optimized (Greedy) Approach:
The "Set Intersection Size At Least Two" problem is elegantly solved by a greedy approach: sort intervals by their end, and for each, add the largest needed numbers to ensure every interval gets two. This minimizes the total set size and leverages overlap between intervals. The key insight is to always pick numbers as late as possible within each interval, maximizing their usefulness for future intervals and ensuring optimality.
class Solution:
def intersectionSizeTwo(self, intervals):
intervals.sort(key=lambda x: (x[1], -x[0]))
res = []
for interval in intervals:
cnt = 0
# Count how many numbers in res are in the interval
for num in reversed(res):
if num >= interval[0]:
if num <= interval[1]:
cnt += 1
if cnt == 2:
break
# Add missing numbers (at most 2)
for x in range(interval[1] - (1 - cnt), interval[1] + 1):
if x not in res and x >= interval[0]:
res.append(x)
return len(res)
class Solution {
public:
int intersectionSizeTwo(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] == b[1] ? a[0] > b[0] : a[1] < b[1];
});
vector<int> res;
for (const auto& interval : intervals) {
int cnt = 0;
for (int i = res.size() - 1; i >= 0; --i) {
if (res[i] >= interval[0] && res[i] <= interval[1]) {
++cnt;
if (cnt == 2) break;
}
}
for (int x = interval[1] - (1 - cnt); x <= interval[1]; ++x) {
if (find(res.begin(), res.end(), x) == res.end() && x >= interval[0])
res.push_back(x);
}
}
return res.size();
}
};
import java.util.*;
class Solution {
public int intersectionSizeTwo(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] != b[1] ? a[1] - b[1] : b[0] - a[0]);
List<Integer> res = new ArrayList<>();
for (int[] interval : intervals) {
int cnt = 0;
for (int i = res.size() - 1; i >= 0; --i) {
int num = res.get(i);
if (num >= interval[0] && num <= interval[1]) {
cnt++;
if (cnt == 2) break;
}
}
for (int x = interval[1] - (1 - cnt); x <= interval[1]; x++) {
if (!res.contains(x) && x >= interval[0]) {
res.add(x);
}
}
}
return res.size();
}
}
var intersectionSizeTwo = function(intervals) {
intervals.sort((a, b) => a[1] === b[1] ? b[0] - a[0] : a[1] - b[1]);
let res = [];
for (let interval of intervals) {
let cnt = 0;
for (let i = res.length - 1; i >= 0; --i) {
let num = res[i];
if (num >= interval[0] && num <= interval[1]) {
cnt++;
if (cnt === 2) break;
}
}
for (let x = interval[1] - (1 - cnt); x <= interval[1]; ++x) {
if (!res.includes(x) && x >= interval[0]) {
res.push(x);
}
}
}
return res.length;
};