带权重的最大不重叠区间子集选择问题
题目描述
给定一个区间集合,每个区间具有起始时间 start、结束时间 end 以及一个正权重 weight。如果两个区间的时间段重叠(包括端点重叠),则它们不能同时被选择。要求选择一个不重叠的区间子集,使得子集中所有区间的权重之和最大。
输入
一个列表 intervals,其中每个元素是三元组 (start, end, weight),且 start < end。区间数量 n 最多可能达到 10^5,但区间端点值范围可能较大。
输出
一个整数,表示最大可能的权重和。
示例
输入:intervals = [(1, 4, 2), (2, 5, 4), (6, 8, 3), (4, 7, 5)]
输出:8
解释:选择区间 (1, 4, 2) 和 (6, 8, 3),权重和为 2 + 3 = 5?不对,实际上可以选 (2, 5, 4) 和 (6, 8, 3) 得到 7,但最优是 (1, 4, 2) 和 (4, 7, 5)?注意 (4, 7, 5) 与 (1, 4, 2) 在端点 4 重叠吗?通常定义中若一个区间结束等于另一个区间开始,视为不重叠(即允许 end == start 不重叠)。若允许,则 (1, 4, 2) 和 (4, 7, 5) 不重叠,权重和 2 + 5 = 7。
但实际最大权重和是选择 (2, 5, 4) 和 (6, 8, 3) 得 7?
我们仔细分析:
(1,4,2)(2,5,4)(6,8,3)(4,7,5)
不重叠组合:
- 选
(1,4,2)后,可接(6,8,3),权重 5。 - 选
(2,5,4)后,可接(6,8,3),权重 7。 - 选
(4,7,5)后,无法再接(6,8,3)(重叠)。 - 选
(6,8,3)单独,权重 3。
最大是 7。
但题目要求最大权重和,可能还有其他组合:选(4,7,5)权重 5,不如 7。
所以示例输出应为 7。
我们再看一个更明显的例子:
[(1, 3, 5), (2, 4, 6), (3, 5, 4), (5, 7, 3)]
最优选(1,3,5)和(3,5,4)和(5,7,3)?不,(1,3,5)与(3,5,4)不重叠(允许 end=start),可以同时选,且(5,7,3)也可接,权重 5+4+3=12。
但若定义不允许 end=start 重叠,则(1,3,5)与(3,5,4)不能同选,最优可能是(2,4,6)和(5,7,3)得 9。
为简化,我们约定:若一个区间的 end 等于另一个区间的 start,视为不重叠(即区间是半开半闭 [start, end) 的表示)。
示例 [(1,4,2), (2,5,4), (6,8,3), (4,7,5)] 中,
(1,4) 与 (4,7) 不重叠,可同选,权重 2+5=7。
(2,5) 与 (4,7) 重叠(因为 (2,5) 包含时间点 4.5 与 (4,7) 重叠),不能同选。
最大权重是 (2,5,4) 和 (6,8,3) 得 7?还是 (1,4,2) 和 (4,7,5) 得 7?都是 7。
实际上,(4,7,5) 权重 5 比 (1,4,2) 的 2 大,选 (4,7,5) 后无法选 (6,8,3)(重叠),所以权重 5+? 只能单独 5,不如 7。
因此示例最大为 7。
题目要求设计一个算法,在 区间数量 n 较大(如 10^5)时也能高效求解。
解题步骤
1. 问题建模
这是一个经典问题:加权区间调度(Weighted Interval Scheduling)。
每个区间 i 有 start[i], end[i], weight[i]。
目标是选出权重和最大的不重叠区间子集。
2. 关键观察
如果所有区间按结束时间 end 从小到大排序,那么我们可以定义:
p(j) = 在区间 j 之前结束且不与 j 重叠的最后一个区间的索引(即最大的 i < j 使得 end[i] <= start[j])。
这样,对于区间 j,有两种选择:
- 不选 j,则最优解是前 j-1 个区间的最优解。
- 选 j,则最优解是
weight[j] + 最优解( p(j) )。
取两者最大值。
3. 动态规划定义
设 dp[i] 表示考虑前 i 个区间(按 end 排序后)能获得的最大权重和。
递推:
dp[i] = max( dp[i-1], weight[i] + dp[ p(i) ] )
其中 p(i) 如上定义。
4. 如何计算 p(i)
对每个区间 i,二分查找在它之前结束且不重叠的最后一个区间。
因为区间已按 end 排序,我们可以对每个 i,在区间 0..i-1 中二分查找 end[k] <= start[i] 的最大 k。
5. 算法流程
- 将所有区间按
end升序排序。 - 计算
p[i]数组(通过二分查找)。 - 初始化
dp[0] = 0(没有区间时权重 0),并虚拟一个区间 0 为占位,实际区间从 1 到 n 编号。
更直接:dp[0] = weight[0]的处理要小心,我们通常让dp[i]对应前 i 个区间(i从1开始)的最大权重和。
定义dp[0] = 0,dp[1] = max(0, weight[1])?其实第一个区间可选可不选,但权重为正肯定选。
更清晰:
排序后区间索引 0..n-1。
dp[i]表示考虑前 i+1 个区间(即区间 0..i)的最大权重和。
递推:
dp[i] = max(dp[i-1], weight[i] + dp[p[i]]),其中p[i]是区间 i 的“前一个不重叠区间”的索引,若不存在则p[i] = -1,此时dp[-1]视为 0。
6. 例子计算
示例:intervals = [(1, 4, 2), (2, 5, 4), (6, 8, 3), (4, 7, 5)]
按 end 排序:
索引 0: (1,4,2)
索引 1: (2,5,4)
索引 2: (4,7,5)
索引 3: (6,8,3)
计算 p[i]:
i=0: start=1,前面没有区间,p[0]=-1
i=1: start=2,查找 end<=2,区间 0 的 end=4>2,没有,p[1]=-1
i=2: start=4,查找 end<=4,区间 0 的 end=4<=4,区间 1 的 end=5>4,所以 p[2]=0
i=3: start=6,查找 end<=6,区间 2 的 end=7>6,区间 1 的 end=5<=6,所以 p[3]=1
dp 计算:
dp[-1]=0
i=0: dp[0] = max(dp[-1]=0, weight[0]=2 + dp[-1]=0) = max(0,2) = 2
i=1: dp[1] = max(dp[0]=2, weight[1]=4 + dp[-1]=0) = max(2,4) = 4
i=2: dp[2] = max(dp[1]=4, weight[2]=5 + dp[0]=2) = max(4,7) = 7
i=3: dp[3] = max(dp[2]=7, weight[3]=3 + dp[1]=4) = max(7,7) = 7
最终 dp[3]=7。
7. 时间复杂度
排序 O(n log n),计算 p[i] 需要 n 次二分查找 O(n log n),dp 计算 O(n)。
总 O(n log n),空间 O(n)。
8. 边界情况
- 区间权重可能全为正,因此至少选一个区间(除非 n=0)。
- 区间端点可能相等,按约定 end==start 不重叠。
- 权重可能为 0,不影响算法。
- n 很大时需注意内存和二分查找实现。
9. 扩展思考
如果要输出具体选择了哪些区间,可以回溯 dp 决策。
从 i=n-1 开始,若 dp[i] == dp[i-1] 说明未选 i,则 i--;否则选了 i,记录 i,跳转到 p[i] 继续。
这样,我们完成了带权重的最大不重叠区间子集选择问题的详细讲解。