[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

  1. 它的开始时间是 start_time
  2. 我们需要在 st 中找到最后一个满足 end_time < start_time 的记录。
  3. lower_bound 找到的是第一个 end_time >= start_time 的位置,因此 --it 就能指向那个“最晚且不重叠”的前一个最优活动。
  4. 将该活动的最大价值 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. 算法亮点

  1. 简洁性:利用 std::rangeslower_bound 极大简化了代码量。
  2. 效率:通过维护一个单调递增的价值表 st,避免了 $O(N^2)$ 的暴力搜索。
  3. 完备性:初始化 st {{0, 0}} 巧妙地处理了“只选一个活动”的情况(即 it 指向 {0, 0},加上的价值为 0)。

5. 易错点提醒

  • 边界条件lower_bound 配合 -- 操作时,必须确保 st 初始有一个极小值(如 {0, 0}),否则迭代器减一可能会越界。
  • 不重叠定义:题目要求 $end_1 < start_2$,因此二分查找的目标必须严格小于当前 start_time
posted @ 2025-12-23 11:49  belief73  阅读(6)  评论(0)    收藏  举报