第九十四天 --- 力扣2054. 两个最好的不重叠活动
题目一
思路:排序重整
1、正常而言,给我们一系列的活动,我们得将之至于时间数轴从前到后的分析,不能直接按照他给的乱序分析,所以这里就要用到排序重整的思路,我们要规定一种方法,能让我们从前到后的分析且不会违反题意。
2、这个题我们就要求找两个活动,让其效益值最大化,那么我们完全可以这么想,我在一个活动开始,就代表着我选择的第二个活动,一个活动结束,就代表着我选择的第一个活动,也就是第一个活动先结束,第二个活动再开始。那么可能朋友会说,如果排在最前面的活动前方没有结束的活动咋办,不要紧,那就认为第一个活动不存在效益值是0即可。
3、所以朴素的想法就是,我先按照活动开始时间排序,再按照活动结束时间排序,首先枚举开始时间作为第二个活动,根据其开始时间就可以限制第一个活动的结束时间在一个固定的范围内,在这个范围内再次寻找效益最大的结束时间对应的活动,这样就保证了先结束,后开始。缺点也极为明显,时间复杂度太高,是O(N^2)。代码见下方。
4、那么既然放在两个数组中维护,就得单独枚举两次,是O(N^2);并且其实第二次枚举只是在固定区间求最大值,这样的话完全没必要枚举,完全可以动态维护;起始时间和终止时间二者本身没啥关系,而且在处理的时候也是分开的,所以分开存储。综上三条,我们把"3"中两个数组合并,用一个Event类存储,op=0代表开始,op=1代表结束,time代表时间,val代表价值。
5、
<1>在写比较函数的时候,就是按照时间先后排序,这也保证了动态维护第一个活动效益最大值的基础,因为我遇到的结束活动,都可以视为第一个选的活动,但是得效益最大,这之后再碰到开始的活动,就可以直接加上这个效益最大值了,他们的顺序是严格的先后关系。
<2>如果时间一致,就得先处理开始活动。我们这里反证法,如果先处理结束活动,有可能会导致从开始时间1到目前,第一个活动最大效益值的更新,等接下来处理第二个活动时候,就会加上刚才更新的值,这样开始和结束时间就重叠了,不合题意。
6、
<1>通过排序得到了一个符合题意的处理顺序,用bestFirst 记录从开始到现在最大效益值是多少,所以每次遇到终止事件的时候,就更新这个值。
<2>遇到开始事件的时候,代表着要选择第二个事件了,因为排序的处理,所以保证了bestFirst 一定严格的在其前面更新的,所以直接使用即可,看看本次第二个时间选择会不会是最大值即可。
7、
<1>其实这个优化算法总体思路和朴素的是一样的,只不过就是把开始和结束放在了一起,因为二者之间没啥关系,所以可以在一起.
<2>在朴素算法中,每找到一个开始时间都得用枚举的手段找其前方已经结束的效益值最大的活动,只因为二者不在一起,现在放在一起存储,有严格的先后处理顺序了,就可以动态维护最大值了,这样O(1)的时间就可以拿到想要的,降低了复杂度。
代码
正确版本
class Event {
public:
int op;//0代表开始、1代表结束
int time;//时间
int val;//效益
Event(int op, int time, int val) {
this->op = op;
this->time = time;
this->val = val;
}
bool operator < (const Event & e) {
if (time == e.time) {
//时间相等优先处理开始活动,防止发生时间重叠
return op < e.op;
}
return time < e.time;//把时间从前到后排序
}
};
class Solution {
public:
int maxTwoEvents(vector<vector<int>>& events) {
int n = events.size();
vector<Event> e;
for (int i = 0; i < n; i++) {
e.push_back({
0,events[i][0],events[i][2] });//放入开始
e.push_back({
1,events[i][1],events[i][2] });//放入结束
}
sort(e.begin(), e.end());//排序
int bestFirst = 0, ans = 0;
for (Event e1 : e) {
if (e1.op == 0) {
//第二个活动开始
ans = max(ans, e1.val + bestFirst);//动态更新
}
else {
bestFirst = max(bestFirst, e1.val);//动态更新第一个活动的效益值
}
}
return ans;
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
超时版本(仅用于参考分析,千万不要用,这个复杂度太高)
class a1 {
public:
int start;
int end;
int val;
bool operator < (const a1 & a) {
return end < a.end;
}
};
class a2 {
public:
int start;
int end;
int val;
bool operator < (const a2 & a) {
return start < a.start;
}
};
class Solution {
public:
int maxTwoEvents(vector<vector<int>>& events) {
int ans = 0;
int n = events.size();
vector<a1> fir;//按照终止时间从小到大排序
vector<a2> sed;//按照起始时间从小到大排序
for (vector<int> tmp : events) {
a1 a11;
a2 a22;
a11.start = tmp[0];
a22.start = tmp[0];
a11.end = tmp[1];
a22.end = tmp[1];
a11.val = tmp[2];
a22.val = tmp[2];
fir.push_back(a11);
sed.push_back(a22);
}
sort(fir.begin(), fir.end());
sort(sed.begin(), sed.end());
for (int i = 0; i < n; i++) {
int tmp_ans = sed[i].val;
int ends = sed[i].start - 1;
int tmp_val = 0;
for (int j = 0; j < n; j++) {
if (fir[j].end <= ends) {
tmp_val = max(tmp_val, fir[j].val);
}
else {
break;
}
}
tmp_ans += tmp_val;
ans = max(ans, tmp_ans);
}
return ans;
}
};