1. 问题背景与核心需求
LeetCode 757题"设置交集大小至少为2"是一个典型的区间处理问题,要求找到一个最小的集合S,使得给定的一组区间中每个区间与S的交集元素不少于2。这类问题在实际应用中经常出现,比如课程时间安排、网络带宽分配等场景。
这个问题的难点在于如何高效地确定哪些元素应该被包含在集合S中,才能以最小的集合大小满足所有区间的交集要求。我们需要设计一个算法,在O(nlogn)时间复杂度内解决这个问题,其中n是区间的数量。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法思路解析
2.1 贪心算法选择
对于这个问题,贪心算法是最合适的解决方案。贪心算法在每一步都做出局部最优选择,希望这些选择能导致全局最优解。在这个问题中,我们需要:
- 首先对区间进行排序
- 然后从右往左处理每个区间
- 在当前区间内选择最右边的两个点加入集合S
这种策略可以最大化这些点对后续区间的覆盖能力,从而最小化集合S的大小。
2.2 区间排序策略
我们首先需要对所有区间进行排序,排序规则是:
- 按区间右端点升序排列
- 如果右端点相同,则按左端点降序排列
这种排序方式可以让我们优先处理结束较早的区间,并且在处理时可以尽可能利用已经选择的点。
java复制Arrays.sort(intervals, (a, b) -> {
if (a[1] != b[1]) return a[1] - b[1];
return b[0] - a[0];
});
3. 算法实现细节
3.1 核心算法步骤
- 对区间进行排序
- 初始化结果集合S为空
- 维护两个变量last和secondLast,表示S中最后加入的两个元素
- 遍历每个区间:
- 如果当前区间与S的交集不足2个元素
- 从当前区间的右端开始向左添加必要元素到S中
- 更新last和secondLast
3.2 关键实现代码
java复制public int intersectionSizeTwo(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> {
if (a[1] != b[1]) return a[1] - b[1];
return b[0] - a[0];
});
List<Integer> res = new ArrayList<>();
res.add(-1); // 哨兵
res.add(-1); // 哨兵
for (int[] interval : intervals) {
int start = interval[0];
int end = interval[1];
int size = res.size();
int last = res.get(size - 1);
int secondLast = res.get(size - 2);
// 当前区间已经包含至少两个元素
if (start <= secondLast) {
continue;
}
// 当前区间包含一个元素
else if (start <= last) {
// 需要添加一个元素
if (last == end) {
res.add(end - 1);
} else {
res.add(end);
}
}
// 当前区间不包含任何元素
else {
res.add(end - 1);
res.add(end);
}
}
return res.size() - 2; // 减去两个哨兵
}
4. 算法正确性证明
4.1 贪心选择性质
我们每次选择当前区间最右边的点加入集合S,这样可以最大化这些点对后续区间的覆盖能力。因为:
- 选择较大的数可以覆盖更多左端点较小的区间
- 从右往左处理可以确保新加入的点不会影响已经处理过的区间
4.2 最优子结构
每个子问题的最优解可以构成原问题的最优解。在处理每个区间时,我们只需要考虑当前区间和已经选择的点,不需要考虑所有区间的全局信息。
5. 复杂度分析
5.1 时间复杂度
- 排序:O(nlogn)
- 遍历处理:O(n)
- 总时间复杂度:O(nlogn)
5.2 空间复杂度
- 存储结果:O(n)(最坏情况下需要存储n个点)
- 总空间复杂度:O(n)
6. 边界条件与特殊案例
6.1 空输入
当输入区间数组为空时,应该返回0,因为不需要任何元素就能满足条件(因为没有区间需要满足交集要求)。
6.2 单元素区间
对于形如[[1,1]]的区间,我们需要在集合S中包含至少两个1才能满足交集大小为2的要求。但实际上根据题目描述,区间[a,b]表示的是a到b之间的整数,所以[1,1]只包含1这一个元素,无法满足交集大小为2的要求。这种情况下题目应该保证输入是有效的。
6.3 完全重叠区间
对于多个完全重叠的区间,如[[1,3],[1,3],[1,3]],我们只需要选择2和3两个点就能满足所有区间的交集要求。
7. 算法优化与变种
7.1 空间优化
我们可以不使用List来存储结果,而是维护最后加入的两个元素,这样可以减少空间复杂度到O(1)。
java复制public int intersectionSizeTwo(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> {
if (a[1] != b[1]) return a[1] - b[1];
return b[0] - a[0];
});
int result = 0;
int last = -1;
int secondLast = -1;
for (int[] interval : intervals) {
int start = interval[0];
int end = interval[1];
if (start <= secondLast) {
continue;
}
else if (start <= last) {
result++;
secondLast = last;
last = (last == end) ? end - 1 : end;
}
else {
result += 2;
secondLast = end - 1;
last = end;
}
}
return result;
}
7.2 交集大小变为k
这个问题可以推广到交集大小至少为k的一般情况。算法思路类似:
- 对区间按右端点排序
- 对于每个区间,检查它与当前集合的交集大小
- 如果不足k,从右端开始添加缺失的元素
8. 实际应用场景
8.1 课程安排
在大学课程安排中,可能需要确保每个时间段都有足够多的教室可用。这个问题可以转化为:每个课程是一个区间,我们需要选择一些时间点(教室开放时间),使得每个课程区间内都有足够多的开放时间点。
8.2 网络带宽分配
在网络流量调度中,可能需要确保某些关键时间段内有足够的带宽资源。这个问题可以建模为区间交集问题,其中带宽分配的时间段是区间,我们需要选择一些时间点来保证每个关键时间段内都有足够的带宽保障。
9. 常见错误与调试技巧
9.1 排序错误
常见的错误是排序策略不正确。正确的排序应该是:
- 主要按右端点升序
- 次要按左端点降序
如果排序不正确,可能导致选择的点无法满足某些区间的交集要求。
9.2 边界条件处理
需要特别注意以下边界条件:
- 区间右端点等于last或secondLast的情况
- 区间长度不足2的情况(根据题目描述应该不会出现)
- 空输入的情况
9.3 调试建议
可以打印出排序后的区间顺序,以及在处理每个区间时last和secondLast的值,这样可以直观地看到算法的执行过程。
10. 与其他LeetCode题目的比较
10.1 与区间覆盖问题的比较
LeetCode 452(用最少数量的箭引爆气球)也是一个区间问题,但只需要保证每个区间至少有一个交点。而本题需要至少两个交点,因此算法更为复杂。
10.2 与会议室问题的比较
LeetCode 253(会议室II)需要计算最多重叠的区间数,而本题需要主动选择点来满足交集要求,两者的解决思路完全不同。
11. 个人实现心得
在实际实现这个算法时,我发现以下几点特别重要:
- 排序策略是关键,正确的排序可以大大简化后续处理
- 维护最后加入的两个点比维护整个集合更高效
- 从右往左处理区间可以最大化点的覆盖能力
- 添加哨兵值可以简化边界条件的处理
在最初实现时,我尝试从左往右处理区间,发现很难保证全局最优。后来改为从右往左处理,并优先选择较大的数,问题就迎刃而解了。这也验证了贪心算法的正确性。
