持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情
在使用动态规划解决问题的时候,有些时候需要我们去优化dp问题的时间复杂度,而二分是一种降低时间复杂度的好手段,一般可以使用其作为优化手段的一种伎俩
题目
1235. 规划兼职工作
你打算利用空闲时间来做兼职工作赚些零花钱。
这里有 n 份兼职工作,每份工作预计从 startTime[i] 开始到 endTime[i] 结束,报酬为 profit[i]。
给你一份兼职工作表,包含开始时间 startTime,结束时间 endTime 和预计报酬 profit 三个数组,请你计算并返回可以获得的最大报酬。
注意,时间上出现重叠的 2 份工作不能同时进行。
如果你选择的工作在时间 X 结束,那么你可以立刻进行在时间 X 开始的下一份工作。
例如:
思路
我们可以首先想到的一件事是贪心,把开始时间或者结束时间从小到大进行排序,然后对齐进行贪心的选取
可是这个问题涉及到了报酬这个概念,并非时间越长越好,有时候,报酬不一定高于其他值。
这个时候如何考虑这一件事情呢?
首先,我们将按照结束时间从小到大进行排序,然后,对于排序好的n个事件,若是我要做这一个事件。
则我至少需要把 这段时间空闲出来,也就是说,若是,形如这种
代表选取到当前事件的最大值,代表第r个事件的最大值。
显然,在第r个事件时,至少要满足r事件的结束时间早于x时间的开始时间即可满足。
可以得到方程式
对于寻找 这一件事情,时间复杂度是的代价,但是,这一点,是可以优化掉的
可以采用二分去寻找小于当前开始时间的最后一个结束时间
最终的时间复杂度为
代码
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]
}
}