435. 无重叠区间
1. 思路
这道题的思路和射箭非常像,因此可以直接利用射箭的结果。但其实复杂了,有更简单的方法。
(1)射箭——左排序
射箭这道题跳过的部分就是这道题需要计数的部分。但是射箭题right>=left需要跳过,本题只要right>left就可以计数。这是因为如果有这样两段,[1,2][2,3],射箭的时候,射到2就可以双穿,所以是>=,但是这道题中这两段交集为1并没有长度,因此不计数。说白了就是点和线的区别。
(2)射箭——逆转结果
可以直接将size()-射箭个数作为结果。但正如(1)中所说的,射箭的判断条件要从right<=left改成right<left。这是因为射箭个数就是不重合片段个数,而重合片段个数最多为size。
(3)右排序
因为重合片段个数最多为size,所以只要顺序后有两个不同线段有交集,就可以计数。所以,不用判断一共有多少个在一个重合中,可以直接遍历顺序右侧,看两两是否有交集。
之所以右排序,因为我们是从左到右遍历的,每跨越一个线段相当于抹除了左侧相交的线断,所以如果用左排序是不合理的。
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int left = intervals[0][0];
int right = intervals[0][1];
int num = 0;
for(int i=1; i<intervals.size(); i++){
left = intervals[i][0];
if(right>left){
num++;
right = min(right,intervals[i][1]);
}else{
right = intervals[i][1];
}
}
return num;
}
};
763. 划分字母区间
1. 思路
思路就是转化到上面那道题,先构建出顺序的区间,之后判断不重复的条件。条件就是当前面一个right<下面一个left。
但其实有更简单的方法。因为这个条件等价于,不同的线段属于不同堆。因此,可以记录字母最后一次出现的位置。然后从前往后遍历,如果遇到max==index,说明这属于一堆。相当于用位置对应位置进行记录。
class Solution {
public:
vector<int> partitionLabels(string S) {
int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置
for (int i = 0; i < S.size(); i++) { // 统计每一个字符最后出现的位置
hash[S[i] - 'a'] = i;
}
vector<int> result;
int left = 0;
int right = 0;
for (int i = 0; i < S.size(); i++) {
right = max(right, hash[S[i] - 'a']); // 找到字符出现的最远边界
if (i == right) {
result.push_back(right - left + 1);
left = i + 1;
}
}
return result;
}
};
56. 合并区间
1. 思路
我的思路是,记录重合区间的起始和重点位置,然后遍历,一个区域结束以后就保存结果。
但是这样做有很多细节需要处理,比如最后一个区间如果是独立的,记录不上。比如只有两个区间,来不及更新。因此需要多写很多条件。
因此,有更简单的方法,就是在result里面不断更新,而不是更新后再存进去。
在结果上更新在很多时候可以显著降低算法分类成本,但是需要算法是线性推进的。贪心算法本质上就是线性推进、全局最优的,因此可以这样做。比如之前震荡数列和气球在原数组上更新left或right,和这道题在result里更新。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> result;
if (intervals.size() == 0) return result; // 区间集合为空直接返回
// 排序的参数使用了lambda表达式
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});
// 第一个区间就可以放进结果集里,后面如果重叠,在result上直接合并
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++) {
if (result.back()[1] >= intervals[i][0]) { // 发现重叠区间
// 合并区间,只更新右边界就好,因为result.back()的左边界一定是最小值,因为我们按照左边界排序的
result.back()[1] = max(result.back()[1], intervals[i][1]);
} else {
result.push_back(intervals[i]); // 区间不重叠
}
}
return result;
}
};