题目描述
你打算利用空闲时间来做兼职工作赚些零花钱。
这里有 n
份兼职工作,每份工作预计从 startTime[i]
开始到 endTime[i]
结束,报酬为 profit[i]
。
给你一份兼职工作表,包含开始时间 startTime
,结束时间 endTime
和预计报酬 profit
三个数组,请你计算并返回可以获得的最大报酬。
注意,时间上出现重叠的 2 份工作不能同时进行。
如果你选择的工作在时间 X
结束,那么你可以立刻进行在时间 X
开始的下一份工作。
样例
输入:startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]
输出:120
解释:
我们选出第 1 份和第 4 份工作,
时间范围是 [1-3]+[3-6],共获得报酬 120 = 50 + 70。
输入:startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]
输出:150
解释:
我们选择第 1,4,5 份工作。
共获得报酬 150 = 20 + 70 + 60。
输入:startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]
输出:6
限制
1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4
1 <= startTime[i] < endTime[i] <= 10^9
1 <= profit[i] <= 10^4
算法
(动态规划,二分) $O(n \log n)$
- 首先将所有工作按照结束时间从小到大排序。
- 设 $f(i)$ 表示考虑了前 $i$ 份工作,能获得的最大利润。
- 初始时 $f(0) = jobs[0][2]$,即第一份工作的利润。
- 对于第 $i$ 份工作,二分查找最大的 $j < i$,使得
jobs[j][1] <= jobs[i][0]
,转移 $f(i) = \max(f(i - 1), f(j) + jobs[i][2])$。若 $j$ 不存在,则转移 $f(j) = \max(f(i - 1), jobs[i][2])$。 - 最终答案为 $f(n - 1)$。
时间复杂度
- 排序的时间复杂度为 $O(n \log n)$,动态规划 + 二分的时间复杂度为 $O(n \log n)$。
- 故总时间复杂度为 $O(n \log n)$。
空间复杂度
- 需要额外 $O(n)$ 的空间重新记录工作的信息方便排序,以及记录动态规划的状态。
C++ 代码
class Solution {
public:
int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
int n = startTime.size();
vector<vector<int>> jobs(n);
for (int i = 0; i < n; i++) {
jobs[i].push_back(startTime[i]);
jobs[i].push_back(endTime[i]);
jobs[i].push_back(profit[i]);
}
auto cmp = [&](const vector<int> &x, const vector<int> &y) {
return x[1] < y[1];
};
sort(jobs.begin(), jobs.end(), cmp);
vector<int> f(n);
f[0] = jobs[0][2];
for (int i = 1; i < n; i++) {
int l = 0, r = i - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (jobs[mid + 1][1] <= jobs[i][0])
l = mid + 1;
else
r = mid;
}
if (jobs[l][1] <= jobs[i][0])
f[i] = max(f[i - 1], f[l] + jobs[i][2]);
else
f[i] = max(f[i - 1], jobs[i][2]);
}
return f[n - 1];
}
};
这题可不可以对左端点排序,然后右端点进行LIS,
厉害!
对于第 ii 份工作,二分查找最大的 j<ij<i,使得 jobs[j][1] <= jobs[j][0],转移 这里是不是该 jobs[j][1] <= jobs[i][0]
手误已修正~