一、435.无重叠区间
本题与昨天的扎气球的那题思路几乎一样,先按照区间左边界进行排序,然后遍历每个区间:
如果当前区间的左边界大于等于上一个区间的右边界,说明没有重叠,不用进行任何操作;
如果当前区间的左边界小于上一区间的右边界,说明重叠,此时需要移除的区间计数加1,并且更新当前区间的右边界为当前区间和上一区间右边界的最小值。
为什么是最小值?和扎气球类似,因为我们要判断后面一个区间和前两个区间是否重叠,只有左边界小于上两个区间重叠的最小右边界,才会同时和两个区间都重叠,重叠的话,需要移除的区间又多一个。
代码如下:
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int count = 0;
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] < intervals[i - 1][1]) { // 当前的左边界小于上一个的右边界,重叠,count++,并更新右边界;当前左边界大于等于上一个的右边界,不做处理
count++;
intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]); // 右边界更新为和上一区间重叠的最小右边界
}
}
return count;
}
};
二、763.划分字母区间
题目链接
本题的关键点有两个:
1)统计每个字母出现的最远范围。这是比较难想到的点,例如在字符串“ababcbacadefegdehijhklij”中a最远出现的位置是下标8,b最远出现的位置是下标5。
怎样统计呢?需要一个哈希数组,遍历每个字母的过程中,字母所对应的哈希值代表它目前出现过最远的范围,按遍历的进程不断更新代码为:
for (int i = 0; i < s.size(); i++) {
hash[s[i] - 'a'] = i; // 统计每个字母出现的最远范围,字母a对应下标0,字母b对应下标1,以此类推
}
2)右边界为遍历过的字母对应的最远出现范围的最大值。怎么判断是否到右边界呢?当i=最大边界的时候就到达了右边界。到了右边界后,收集左边界和右边界的结果,并把左边界更新为右边界+1。
更新右边界的代码:
right = max(right, hash[s[i] - 'a']); // 右边界为遍历过的字母对应的最远出现范围的最大值
收集结果并更新左边界的代码,注意收集的结果是区间的大小,即区间中字母的个数,要加1:
if(i == right) { // 当i遍历到最远右边界时
result.push_back(right - left + 1) ; // 收集的结果是区间的大小,也即区间中字母的个数,所有要加1
left = right + 1; // 更新左区间
}
整体代码如下:
class Solution {
public:
vector<int> partitionLabels(string s) {
int hash[27] = {0};
for (int i = 0; i < s.size(); i++) {
hash[s[i] - 'a'] = i; // 统计每个字母出现的最远范围,字母a对应下标0,字母b对应下标1,以此类推
}
int left = 0;
int right = 0;
vector<int> result;
for (int i = 0; i < s.size(); i++) {
right = max(right, hash[s[i] - 'a']); // 右边界为遍历过的字母对应的最远出现范围的最大值
if(i == right) { // 当i遍历到最远右边界时
result.push_back(right - left + 1) ; // 收集的结果是区间的大小,也即区间中字母的个数,所有要加1
left = right + 1; // 更新左区间
}
}
return result;
}
};
三、56.合并区间
题目链接
拿到这道题第一反应是在原区间intervals中进行修改,但是实际操作起来非常麻烦,因为还要涉及删除元素,并且区间的下标也会改变。
卡哥的解法中非常巧妙的一点在于:额外使用一个result数组来记录结果,比较区间边界时直接与reuslt中的最后一个元素进行比较。
这样做法的好处在于,不用修改原数组,同时result数组可以随时更新右边界,并直接收获结果。
注意:直接在result中更新区间时,只需要更新右边界,因为左边界已经排过序,上一个的左边界一定是小的。
关键一步的代码:
if (intervals[i][0] <= result.back()[1]) {
result.back()[1] = max(result.back()[1], intervals[i][1]); // 和上一个区间边界重叠,更新右边界
代码如下:
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> result;
if (intervals.size() == 0) return result; // 不要忘记
sort(interals.begin(), intervals.end(), cmp); // 让重叠区间尽可能挨在一起
result.push_back(intervals[0]); // 把第一个区间放进结果集中,如果后面重叠,直接在result中进行合并
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] <= result.back()[1]) { // 当前区间和上一个区间重叠了,直接和result中的比较,因为result中寸的就是上一个区间
result.back()[1] = max(result.back()[1], intervals[i][1]); // 直接在result中更新区间,只需要更新右边界,因为左边界已经排过序,上一个的左边界一定是小的
}else { // 当前区间和上一个区间没有重叠
result.push_back(intervals[i]); // 加入到result中
}
}
return result;
}
};
总结
重叠区间的题目总体难度不高,也比较容易理解,但有以下细节需要注意:
1)不要忘记对区间排序;
2)当区间长度为0时的if判断不要漏掉;
3)更新区间的时候,取最大值还是最小值不要想当然。