[2054. 两个最好的不重叠活动
2054. 两个最好的不重叠活动 - 力扣(LeetCode)
进阶:1751. 最多可以参加的会议数目 II - 力扣(LeetCode)
class Solution {
public:
int maxTwoEvents(vector<vector<int>>& events) {
ranges::sort(events, {}, [](auto& e) { return e[1]; });
vector<pair<int, int>> st {{0, 0}};
int ans = 0;
for (auto& e : events)
{
int start_time = e[0], value = e[2];
auto it = --ranges::lower_bound(st, start_time, {}, &pair<int, int>::first);
ans = max(ans, it->second + value);
if (value > st.back().second)
{
st.emplace_back(e[1], value);
}
}
return ans;
}
};
1. 核心思路
该算法旨在从给定的活动中选择 最多两个 不重叠的活动,使得它们的价值之和最大。
- 排序:首先按活动的 结束时间 (
e[1]) 进行升序排序。 - 记录历史最大值:使用一个数组
st(类似于单调栈或前缀最大值表) 来存储(结束时间, 到该时间为止的最大价值)。 - 贪心与匹配:遍历每个活动时,利用二分查找在
st中寻找一个结束时间早于当前活动开始时间的“历史最强活动”,将其价值与当前活动价值相加,更新全局最大值。
2. 代码逻辑分解
A. 预处理与排序
ranges::sort(events, {}, [](auto& e) { return e[1]; });
按结束时间排序的目的是为了让我们在遍历时,可以保证 st 中的时间点是递增的,从而可以使用二分查找。
B. 维护“前缀最大价值”表
vector<pair<int, int>> st {{0, 0}}; // 格式: {结束时间, 该时间点前的最大 value}
st存储的是一个个“里程碑”。- 如果当前活动的价值比之前记录的所有活动价值都大,就把它存入
st。这样st中的second(价值)也是单调递增的。
C. 二分查找匹配
auto it = --ranges::lower_bound(st, start_time, {}, &pair<int, int>::first);
ans = max(ans, it->second + value);
对于当前活动 e:
- 它的开始时间是
start_time。 - 我们需要在
st中找到最后一个满足end_time < start_time的记录。 lower_bound找到的是第一个end_time >= start_time的位置,因此--it就能指向那个“最晚且不重叠”的前一个最优活动。- 将该活动的最大价值
it->second与当前value相加。
D. 更新状态
if (value > st.back().second) {
st.emplace_back(e[1], value);
}
如果当前活动的价值创了新高,就记录下它的结束时间和这个新高度。即使 value 没有创新高,由于我们已经按结束时间排序,当前的 ans 也会在之前的步骤中被更新。
3. 复杂度分析
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | $O(N \log N)$ | 主要是排序耗时 $O(N \log N)$,后续遍历中每次二分查找耗时 $O(\log N)$。 |
| 空间复杂度 | $O(N)$ | 需要一个辅助数组 st 来存储前缀最大值,最坏情况下存储所有活动。 |
4. 算法亮点
- 简洁性:利用
std::ranges和lower_bound极大简化了代码量。 - 效率:通过维护一个单调递增的价值表
st,避免了 $O(N^2)$ 的暴力搜索。 - 完备性:初始化
st {{0, 0}}巧妙地处理了“只选一个活动”的情况(即it指向{0, 0},加上的价值为 0)。
5. 易错点提醒
- 边界条件:
lower_bound配合--操作时,必须确保st初始有一个极小值(如{0, 0}),否则迭代器减一可能会越界。 - 不重叠定义:题目要求 $end_1 < start_2$,因此二分查找的目标必须严格小于当前
start_time。
浙公网安备 33010602011771号