这两个题目的思路都是一样的. 都是在经典的任务安排问题上变种过来的. 先看435题:
给一堆的区间, 其中有的区间之间是相互重叠的. 我们需要删除其中的一些区间. 使得剩下的区间之间没有重叠 (注意: [1, 2], [2,3]两区间并没有重叠, 只是紧邻). 要求找到最少删除数.
Input: [ [1,2], [2,3], [3,4], [1,3] ]
Output: 1
我们只需要删除[1, 3]区间就可以了
这个问题的主体思路是贪心算法. 最少删除也就对应着最多的保留量. 所以我们找一个方案尽量多地保留区间.
我们每次从剩余区间中取最早结束的那个区间将它保留下来, 其结束时间记为 ei , 其他区间V[k]如果开始时间大于 ei , 那么V[k]只能被删除.
a证明: 数学归纳法.
就假设用我们的算法得到的解是A
{a1,a2,...,am}
, 最佳解是O
{b1,b2,...,bn}
, 我们将证明m=n:
记题目给的区间总数为N.
1. 当n=1的时候表示最佳方案只挑选了第一个区间, m= 1, n= 1.显然成立.
aend1≤bend1
2. 假如当n=i-1的时候命题成立, 即A集合的大小等于O集合的大小. 我们要证明n=i的时候命题也成立, :因为
aendi−1≤bendi−1,bendi−1≤bstarti
根据我们上面的算法, 每次往A里面新添加的都是剩余的最早结束区间, 所以
aendi≤bendi
.
综上我们可以得到 am≤bn 的, 所以处理完所有的区间后, A的大小一定是大于等于O的大小的. 所以A就是最佳解.
int eraseOverlapIntervals(vector<Interval>& intervals) {
if(0 == intervals.size()) {
return 0;
}
auto cmp_end = [](const Interval & l, const Interval & r) {
return l.end < r.end;
};
std::sort(intervals.begin(), intervals.end(), cmp_end);
int e = intervals[0].end;
int cnt = 1;
for(size_t i = 1; i < intervals.size(); ++i) {
if(intervals[i].start < e) {
continue;
}
e = intervals[i].end;
++cnt;
}
return intervals.size() - cnt;
}
在实现的时候有个小技巧. 按照end排序之后. 依次按照end从低到高遍历区间. 记录上一个被选择的区间的end值. 如果现在的这个区间的start小于prev_end. 说明这个区间早就应该被删除的. 所以丢弃这个区间. 这样我们每次添加一个区间之后, 并不需要再去找与它重叠的区间.
leetcode 452的打气球问题也是一样的思路. 每个气球最后都需要被打爆, 我们需要尽量做到”一石二鸟”, “一石N鸟”. 每次从剩余的气球中找end最小的那个, 打爆这个气球的朝着它的end的位置发射, 这样能尽可能地多带几个其他的气球一起爆掉.
int findMinArrowShots(vector<pair<int, int>>& points) {
using Point=pair<int, int>;
const int sz = points.size();
if(0 == sz) {
return 0;
}
auto cmp_end = [](const Point & l, const Point & r) {
return l.second< r.second;
};
sort(points.begin(), points.end(), cmp_end);
int cnt = 1;
int prev_e = points[0].second;
for(int i = 1; i < sz; ++i) {
if(points[i].first<= prev_e) {
continue;
}
prev_e = points[i].second;
++cnt;
}
return cnt;
}