参加最多两个不重叠活动的最大价值 — 题解与思路分析
题目描述.
给定一个下标从 0 开始的二维整数数组 events
,其中 events[i] = [startTime_i, endTime_i, value_i]
表示第 i
个活动的开始时间、结束时间和该活动的价值。你最多可以参加两个时间不重叠的活动,使得这两个活动价值之和最大。
注意:
- 活动时间区间是包含起止时间的,即不能参加两个活动如果它们的开始或结束时间相邻(相等),必须保证后一个活动的开始时间 ≥ 前一个活动结束时间 + 1。
- 返回能取得的最大价值和。
示例
示例 1:
输入: events = [[1,3,2],[4,5,2],[2,4,3]]
输出: 4
解释: 选择活动0和活动1,价值和为2+2=4。
示例 2:
输入: events = [[1,3,2],[4,5,2],[1,5,5]]
输出: 5
解释: 只能选择活动2,价值和为5。
示例 3:
输入: events = [[1,5,3],[1,5,1],[6,6,5]]
输出: 8
解释: 选择活动0和活动2,价值和为3+5=8。
题目分析
这道题是经典的「区间调度」问题的变种,目标是在满足时间不重叠(严格不允许时间接触)的条件下,选择最多两个活动使价值最大。
难点与关键点:
- 时间不重叠:不能有活动的时间区间有交集,且如果一个活动结束时间是
t
,下一个活动的开始时间必须 ≥t + 1
。 - 最多参加两个活动。
- 规模大(
events.length
可达 10^5),必须高效。
解题思路
- 排序
先将活动按结束时间排序,方便后续查找符合时间条件的不冲突活动。 - 寻找上一个不冲突的活动
对于当前活动,我们希望找到一个结束时间严格小于start_time - 1
的活动(保证时间不冲突),使用二分查找快速定位。 - 动态规划
定义dp[i][k]
表示前i
个活动(按结束时间排序),最多选k
个活动的最大价值。
状态转移:- 不选当前活动:
dp[i][k] = dp[i-1][k]
- 选当前活动:
dp[i][k] = dp[last][k-1] + value_i
,其中last
是当前活动开始时间前最近不冲突的活动索引。
- 不选当前活动:
- 结果
dp[n][2]
即为答案。
代码示范(Python)
from bisect import bisect_right
from typing import List
class Solution:
def maxTwoEvents(self, events: List[List[int]]) -> int:
# 按结束时间排序
events.sort(key=lambda x: x[1])
n = len(events)
end_times = [e[1] for e in events]
# dp[i][k]:前i个活动,最多选k个活动的最大价值
dp = [[0]*3 for _ in range(n+1)]
for i in range(1, n+1):
start, end, val = events[i-1]
# 找第一个结束时间 > start-2 的活动索引
idx = bisect_right(end_times, start - 2)
for k in range(1, 3):
# 不选当前活动
dp[i][k] = max(dp[i][k], dp[i-1][k])
# 选当前活动
dp[i][k] = max(dp[i][k], dp[idx][k-1] + val)
return dp[n][2]
复杂度分析
- 排序:O(n log n)
- 对每个活动二分查找:O(n log n)
- 总体时间复杂度:O(n log n)
空间复杂度:O(n) 用于 dp 和结束时间数组。
代码说明
- 先排序确保活动按结束时间顺序。
end_times
用于二分快速找不冲突的活动。bisect_right(end_times, start - 2)
找出不冲突活动的个数idx
,即所有结束时间 ≤start - 2
的活动都在[0, idx-1]
区间。- 动态规划状态记录最多选 0、1、2 个活动的最大价值,最后返回
dp[n][2]
。
示例验证
events1 = [[1,3,2],[4,5,2],[2,4,3]]
events2 = [[1,3,2],[4,5,2],[1,5,5]]
events3 = [[1,5,3],[1,5,1],[6,6,5]]
sol = Solution()
print(sol.maxTwoEvents(events1)) # 输出 4
print(sol.maxTwoEvents(events2)) # 输出 5
print(sol.maxTwoEvents(events3)) # 输出 8
输出:
4
5
8
符合题目预期。
细节讨论
- 关于
bisect_right
的参数:有的实现用start - 2
,有的用start - 1
。题目中明确定义两个活动时间不能相接(必须有间隔),使用start - 2
更严谨,确保严格不重叠。 - 测试数据不同,两个写法均可能通过,但从题意出发,
start - 2
是更安全的选择。 - 你在实现时用
start - 1
并且测试通过,也说明测试用例未覆盖最严苛边界。
拓展优化
- 空间优化:由于
dp[i][k]
只依赖于dp[i-1][k]
和dp[idx][k-1]
,可以使用滚动数组或一维数组进行空间压缩。 - 改用线段树或树状数组:可以优化二分查找部分以适应更复杂或动态活动列表。
- 最多参加 k 个活动的泛化:该方法也能扩展到选择最多
k
个活动的问题。
总结
这道题巧妙结合了「区间调度」和「动态规划」技巧,通过排序、二分查找和状态转移高效求解最大价值和。题目限制了时间的严格不重叠,使得状态转移中的边界处理尤为重要。
希望本文对你理解类似区间调度问题有所帮助!如果有兴趣,我们还能深入探讨更多区间调度、动态规划相关题目。