持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情
题目描述
2022/10/22 每日一题
难度:困难
有 n
份兼职工作,每份工作预计从 startTime[i]
开始到 endTime[i]
结束,报酬为 profit[i]
,请你计算并返回可以获得的最大报酬。注意,时间上出现重叠的 2 份工作不能同时进行。如果你选择的工作在时间 X 结束,那么你可以立刻进行在时间 X 开始的下一份工作。
输入:给定三个数组
startTime[n]
endTime[n]
profit[n]
输出:时间不冲突的情况下可以获取的最大报酬
算法思路
对于兼职表中的一份工作,有做和不做两种选项,如果选择做,可能会与后续出现的报酬更大的工作出现时间冲突而造成最终报酬不是最大报酬,所以要对每个工作的做与不做的情况都充分考虑,并记录此时的最大报酬,因此选择使用动态规划。
动态规划
步骤:
- 设置数组
dp[n + 1]
记录前i份工作的最大报酬 - 对于
dp[i]
,为当第i - 1
份工作到来时,可以选择做或不做 - 做此份工作则需要往前寻找不与此份工作冲突的最大报酬
dp[k]
加上当前工作报酬profit[n]
;不做此份工作则此时最大报酬为dp[i - 1]
。dp[i]
取做此工作与不做此工作之间的最大报酬。即此动态规划的状态转移方程为:
其中k为不与当前工作冲突时可取得的最大报酬,即工作k的结束时间<=工作i的开始时间
- 将
startTime[n]
、endTime[n]
和profit[n]
三个数组按对应关系存入二维数组jobs[n][3]
中,其中n为工作的数量,3代表给定的三个数组。 - 按结束时间的升序对jobs排序:这样方便搜索不与当前工作开始时间冲突的结束时间的工作
改进的二分查找
普通的二分查找为找到目标值就返回目标在数组的中的下标,否则返回-1。
本题中的二分查找稍有不同,返回值为序列中最后一个 <= 目标值的元素的下标,此时若使用普通的二分查找会出现一种情况:目标值在序列中有多个,若遇到等于目标值的元素就返回,那么得到的元素不是符合题意中“最后一个<= 目标值的元素”。因此可以转换为求第一个 > 目标值的元素的下标。
因此,定义二分查找函数BinarySearch(int target, int right, int[][] jobs)
:
target
:二分查找的目标,即当前工作的开始时间
right
:二分查找的右边界,传入当前工作结束时间(无需将右边界设置为整个结束时间数组的右边:目标 <= 当前工作开始时间 < 当前工作结束时间,目标不会超出当前工作结束时间)
jobs
:工作属性二维数组
改进的二分查找与普通的二分查找之间的区别:
1、边界为left < right,此时返回left:因为题意要求必须返回一个下标,因此left = right时无需再查找,直接返回left;
2、left不能等于mid,若要移动left
必须将其移动到mid + 1
,因为mid只会与left重合,若在left = mid的时候出现target > nums[mid], 此时执行left = mid会陷入死循环;
3、若target >= mid ,left移至mid + 1(允许left对应的元素 > target);
4、若target < mid,right移至mid(不允许right对应的元素 < target);
5、准确来说返回值要减1才符合题意,但本题的dp与jobs之间的序号差1,从jobs中返回的 +1 序号在dp中直接使用;
6、搜索“第一个 >= 目标值的元素”同样可以转换为求最后一个 < 目标值的元素的下标。
代码实现
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
int n = startTime.length;
int[][] jobs = new int[n][];
for(int i = 0; i < n; i++){
jobs[i] = new int[]{startTime[i], endTime[i], profit[i]};
}
Arrays.sort(jobs, (a, b) -> a[1] - b[1]);
int[] dp = new int[n + 1];
for(int i = 1; i <= n; i++){
int k = binarySerch(jobs[i - 1][0], i - 1, jobs);
dp[i] = Math.max(dp[i - 1], dp[k] + jobs[i - 1][2]);
}
return dp[n];
}
public int binarySerch(int target, int right, int[][] jobs){
int left = 0;
while(left < right){
int mid = left + (right - left) / 2;
if(target >= jobs[mid][1]){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}