1751. 最多可以参加的会议数目 II
给你一个 events
数组,其中 events[i] = [startDayi, endDayi, valuei]
,表示第 i
个会议在 startDayi
天开始,第 endDayi
天结束,如果你参加这个会议,你能得到价值 valuei
。同时给你一个整数 k
表示你能参加的最多会议数目。
你同一时间只能参加一个会议。如果你选择参加某个会议,那么你必须 完整 地参加完这个会议。会议结束日期是包含在会议内的,也就是说你不能同时参加一个开始日期与另一个结束日期相同的两个会议。
请你返回能得到的会议价值 最大和 。
与1235.规划兼职工作相似,在前一题的基础上,添加了最多能参加 k 个会议的要求。
题目要求:在参加不超过 k 次会议的前提下,选择会议,得到最大值。
这与 01 背包有些相似:求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。每个物品的状态只有两种:0,不选;1,选。
本题中,会议也只有两种状态:参加或不参加。
理解了题意,发现可能可以用 01 背包做,重点是判断会议能不能参加(物品能不能放入背包)。
然后进入 dp 常规步骤
1、状态定义 2、状态计算 3、初始化 4、确定遍历顺序
1.状态定义
与 01 背包相似
int[][] f = new int[n][k];
f[i][j]
表示在前 i 个会议中选择,不超过 j 次能获得的最大结果。
2.状态计算
设当前会议为 ev。
与背包问题不同,本题中两个会议之间存在先后关系。所以要进行排序,保证 f[i][j]
能由前 i 个状态转移而来。按照结束时间 end 排序。
Arrays.sort(events,(x,y) -> {
return x[1] - y[1];
});
对于状态 f[i][j]
,会议 ev 有两种状态:
1.不选 f[i][j] = f[i - 1][j]
2.选,在会议时间不冲突的前提下 ev.start > lastEv.end
, f[i][j] = f[?][j - 1] + ev.val
。
在选的过程中,枚举之前的状态,如果有两个选择 f[i0]
,f[i1]
i1 > i0,分别表示在前 i0 个会议中选,在前 i1 个会议中选,f[i1]
一定是大于等于 f[i0]
的,因为会议已经按结束时间排序,回看状态定义,f[i1]
包含f[i0]
。
所以对于 f[i][j]
只需要逆向枚举 f[i - 1]
到 f[1]
,找到第一个符合要求的状态 i,进行转移,即可获得最大值。
int last = 0;
for(int j = i - 1; j >= 1; j--){
int[] jv = events[j - 1];
if(jv[1] < st){
last = j;
break;
}
}
for(int u = 1; u <= k; u++){
f[i][u] = Math.max(f[i - 1][u],f[last][u - 1] + v);
}
3.初始化
特判会议数 = 0,特判 k = 0。结果都是 0,不需要初始化。
4.确定遍历顺序
f[i][j]
从f[0...i][0...j]
转移而来,所以i,j从小到大遍历
Java 代码
class Solution {
public int maxValue(int[][] events, int k) {
int n = events.length;
int[][] f = new int[n + 1][k + 1];
Arrays.sort(events,(x,y) -> {
return x[1] - y[1];
});
int res = 0;
for(int i = 1; i <= n; i++){
int[] cur = events[i - 1];
int st = cur[0];
int ed = cur[1];
int v = cur[2];
int last = 0;
for(int j = i - 1; j >= 1; j--){
int[] jv = events[j - 1];
//贪心
if(jv[1] < st){
last = j;
break;
}
}
for(int u = 1; u <= k; u++){
f[i][u] = Math.max(f[i - 1][u],f[last][u - 1] + v);
}
}
return f[n][k];
}
}