首先是题目的大致描述:这个理解起来不难(题目读起来越简单,心里越发慌…)
你打算利用空闲时间来做兼职工作赚些零花钱。
这里有 n 份兼职工作,每份工作预计从 startTime[i] 开始到 endTime[i] 结束,报酬为 profit[i]。
给你一份兼职工作表,包含开始时间 startTime,结束时间 endTime 和预计报酬 profit 三个数组,请你计算并返回可以获得的最大报酬。
注意,时间上出现重叠的 2 份工作不能同时进行。
如果你选择的工作在时间 X 结束,那么你可以立刻进行在时间 X 开始的下一份工作。
总结一下原题描述就是:我们需要选择不重复的若干工作进行利润的最大化,类似于工序安排问题,但是这里加了个利润最大化,这题数据范围很玄学是5*10^4,其实看到这个大家肯定能反映过来二维DP肯定是行不通的了,超时的数据范围,那么如果是O(n)的时间复杂度呢,我们有什么好的解决办法吗,其实这题我第一时间想到的是贪心思想,但是想想好像不合理,因为如果按照结束时间排序进行贪心,如果最后一个利润搞的超级大,大过了所有那么肯定是贪心失败,那么如果按照利润进行贪心呢,显然也不行,不能考虑到分配时间的合理性,所以这个题目只能是DP来解决。这类题目最常规的思想就是按照结束时间进行排序,那么我们考虑一下这个有序的结束时间,其实有个思路就是可不可以考虑二分是思想,那么时间复杂度可以优化到O(nlogn)其实这个思路是可行的,我们针对当前的任务的开始时间,找到结束时间最接近该任务的对于其整个利润进行最大化更新是不是就可以得到全局的最优解了,显然是正确的对吧,如果没有找到那么就是当前任务本身的利润和dp[i-1]的利润取最大值,这种就是会兼顾到我前面所说的那个最后一个加入超级大,大过所有的利润和,那么我们就会更新max_profit,所以这个题整个的思想是这么分析的,这里还有个手写二分的技巧,在代码里给大家分析出来了(我们无需进行判断最后的正确结果是存在于l还是r中,我们就搞一个额外变量存一下我们的合理下标即可)
class Solution {
public:
int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
int n = startTime.size();
vector<array<int, 3>> jobs(n);
for(int i = 0;i < n; i++){
jobs[i] = {endTime[i], startTime[i], profit[i]};
}
sort(jobs.begin(), jobs.end(), [](auto &a, auto &b){return a[0] < b[0];});//这个排序技巧大家可以学习一下,代码很简洁
vector<int>dp(n + 1);
dp[1] = jobs[0][2];//初始化第1个任务最大利润就是自身
for(int i = 1;i < n; i++){//从第一个任务开始找
int l = 0, r = i - 1;//l是找寻范围左边界,r是有边界
int tmp = jobs[i][1], index = -1;//index保存我们的结果下标,也就是最接近tmp的endTime
while(l <= r){
int mid = (l + r) / 2;
if(jobs[mid][0] > tmp){//这里是不合法情况 去左侧区间找
r = mid - 1;
}else{
index = mid;//合法区间是这里所以我们用index来记录我们的合法小标 这里很关键,我们用一个index来存储合法小标就避免最后分析到底是l还是r或者l-1,r+1
l = mid + 1;
}
}
if(index != -1){//如果不为-1,那么显然是找到了1个index下标 进行更新 这这里index+1是因为我们的dp数组是从1开始的
dp[i + 1] = max(dp[i], dp[index + 1] + jobs[i][2]);
}else{//没找到就是 自身的利润和dp[i]比大小 取最大值
dp[i + 1] = max(dp[i], jobs[i][2]);
}
cout << index << ' ' << dp[i + 1] << endl;
}
return dp[n];
}
};