技术岗位 · Coding

加权不重叠工作:按结束时间DP与二分前驱

选择任意工作子集,区间不重叠,利润和最大。

题目重点:选择任意工作子集,区间不重叠,利润和最大。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • job=(start,end,profit),半开且start<end;负利润可跳过,允许空方案。
  • 工作不可拆分,结束恰等下一起点可接;只求利润,不返回计划。

解题路线

  1. 按end排序,dp[i]表示前i项最优。当前任务take需加上所有end≤start的前驱前缀答案,skip为dp[i]。
  2. bisect_right在此前的end数组找符合条件的项数,dp前缀下标恰对应该count。只在0..i里找防止考虑当前/未来任务。
  3. 每个最优方案要么不选当前,要么选它并搭配兼容前缀最优,因此递推完整。不能按profit最高先选,因为长任务可能挡住多个高合计利润短任务。

手动走一遍

(0,3,5),(1,2,4),(2,3,4):选后两项利润8胜过第一项5。(0,1,−2)可空方案0;若要求至少一项,需要独立状态。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from bisect import bisect_right


def best_schedule(jobs):
    jobs = sorted(jobs, key=lambda x:x[1])
    if any(s >= e for s,e,p in jobs):
        raise ValueError("positive-duration half-open jobs required")
    ends = [e for s,e,p in jobs]
    dp = [0]
    for i,(start,end,reward) in enumerate(jobs):
        count = bisect_right(ends, start, 0, i)
        dp.append(max(dp[-1],dp[count]+reward))
    return dp[-1]

复杂度

O(nlogn)时间、O(n)空间,排序及每个前驱二分;重建计划需choice/predecessor数组。

易错点与追问

容易失分的边界

  • 把bisect_left用于等终点会禁止本可接的端点任务。
  • 求最大可选数量的按结束时间贪心不适合加权利润。

继续深挖

  • K台server并行任务怎样转成流或更高维DP?
  • 如果start/end流式到达但允许取消,为什么离线排序前提消失?
✓已复制微信号!