动态规划+二分搜索优化dp

90 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情

在使用动态规划解决问题的时候,有些时候需要我们去优化dp问题的时间复杂度,而二分是一种降低时间复杂度的好手段,一般可以使用其作为优化手段的一种伎俩

题目

1235. 规划兼职工作

你打算利用空闲时间来做兼职工作赚些零花钱。

这里有 n 份兼职工作,每份工作预计从 startTime[i] 开始到 endTime[i] 结束,报酬为 profit[i]

给你一份兼职工作表,包含开始时间 startTime,结束时间 endTime 和预计报酬 profit 三个数组,请你计算并返回可以获得的最大报酬。

注意,时间上出现重叠的 2 份工作不能同时进行。

如果你选择的工作在时间 X 结束,那么你可以立刻进行在时间 X 开始的下一份工作。

例如:

image.png

image.png

image.png

思路

我们可以首先想到的一件事是贪心,把开始时间或者结束时间从小到大进行排序,然后对齐进行贪心的选取

可是这个问题涉及到了报酬这个概念,并非时间越长越好,有时候,报酬不一定高于其他值。

这个时候如何考虑这一件事情呢?

首先,我们将按照结束时间从小到大进行排序,然后,对于排序好的n个事件,若是我要做这一个事件。

则我至少需要把 这段时间空闲出来,也就是说,若是,形如这种 f[x]=f[r]+vf[x] = f[r] + v

f[x]f[x] 代表选取到当前事件的最大值,f[r]f[r]代表第r个事件的最大值。

显然,在第r个事件时,至少要满足r事件的结束时间早于x时间的开始时间即可满足。

可以得到方程式 f[x]=min(f[x1],f[r]+v)f[x] = min(f[x-1], f[r] + v)

对于寻找 f[r]f[r] 这一件事情,时间复杂度是O(N)O(N)的代价,但是,这一点,是可以优化掉的

可以采用二分去寻找小于当前开始时间的最后一个结束时间

最终的时间复杂度为O(nlog2n)O(nlog_2n)

代码

use std::cmp::{max, min};
impl Solution {
    pub fn job_scheduling(start_time: Vec<i32>, end_time: Vec<i32>, profit: Vec<i32>) -> i32 {
        let n = start_time.len();
        let mut f = vec![0; n + 1];
        fn upper(time: &Vec<(i32, i32, i32)>, val: i32) -> usize {
            let (mut l, mut r) = (0, time.len());
            while l < r {
                let mid = l + r >> 1;
                if time[mid].1 <= val {
                    l = mid + 1;
                } else {
                    r = mid;
                }
            }
            l
        }
        let mut pack: Vec<(i32, i32, i32)> = Vec::new();
        for i in 0..n {
            pack.push((start_time[i], end_time[i], profit[i]));
        }
        pack.sort_by(|x, y| x.1.cmp(&y.1));

        for i in 1..=n {
            f[i] = max(f[i - 1], f[upper(&pack, pack[i - 1].0)] + pack[i - 1].2);
        }

        f[n]
    }
}