算法刷题 Day 36 | ● 435. 无重叠区间 ● 763.划分字母区间 ● 56. 合并区间
今天的三道题目,都算是 重叠区间 问题,大家可以好好感受一下。 都属于那种看起来好复杂,但一看贪心解法,惊呼:这么巧妙!
还是属于那种,做过了也就会了,没做过就很难想出来。
不过大家把如下三题做了之后, 重叠区间 基本上差不多了
435.无重叠区间
https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html
Tips:排序的写法很重要,根据什么东西来进行排序也很重要。
按照右边界排序,就要从左向右遍历,因为右边界越小越好,只要右边界越小,留给下一个区间的空间就越大,所以从左向右遍历,优先选右边界小的。
按照左边界排序,就要从右向左遍历,因为左边界数值越大越好(越靠右),这样就给前一个区间的空间就越大,所以可以从右向左遍历。
如果按照左边界排序,还从左向右遍历的话,其实也可以,逻辑会有所不同。
我来按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了。
此时问题就是要求非交叉区间的最大个数。
右边界排序之后,局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间大一些,从而尽量避免交叉。全局最优:选取最多的非交叉区间。
局部最优推出全局最优,试试贪心!
这里记录非交叉区间的个数还是有技巧的,如图:

区间,1,2,3,4,5,6都按照右边界排好序。
每次取非交叉区间的时候,都是可右边界最小的来做分割点(这样留给下一个区间的空间就越大),所以第一条分割线就是区间1结束的位置。
接下来就是找大于区间1结束位置的区间,是从区间4开始。那有同学问了为什么不从区间5开始?别忘了已经是按照右边界排序的了。
区间4结束之后,再找到区间6,所以一共记录非交叉区间的个数是三个。
总共区间个数为6,减去非交叉区间的个数3。移除区间的最小数量就是3。
我的题解:
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b){
return a[1] < b[1];
}
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size() == 0) return 0;
int count = 1;
sort(intervals.begin(), intervals.end(), cmp);
int rightEnd = intervals[0][1];
for(int i = 1; i<intervals.size(); i++){
if(intervals[i][0] >= rightEnd){
count++;
rightEnd = intervals[i][1];
}
}
return intervals.size() - count;
}
};
763.划分字母区间
https://programmercarl.com/0763.%E5%88%92%E5%88%86%E5%AD%97%E6%AF%8D%E5%8C%BA%E9%97%B4.html
Tips:
我的题解:
1. 最开始自己想的解法(英文是26个字母不是24个啊啊啊啊,就在这写错了没过),与452.用最少数量的箭引爆气球 (opens new window)、435.无重叠区间 (opens new window)相同的思路。
统计字符串中所有字符的起始和结束位置,记录这些区间(实际上也就是435.无重叠区间 (opens new window)题目里的输入),将区间按左边界从小到大排序,找到边界将区间划分成组,互不重叠。找到的边界就是答案。
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b){
return a[0] < b[0];
}
public:
vector<int> partitionLabels(string s) {
vector<vector<int>> alphabetTemp(26);
for(int i = 0; i<s.size(); i++){
alphabetTemp[s[i] - 'a'].push_back(i);
}
vector<vector<int>> alphabet;
for(int i = 0;i<26;i++){
if(alphabetTemp[i].size() > 0){
alphabet.push_back(alphabetTemp[i]);
}
}
sort(alphabet.begin(),alphabet.end(),cmp);
vector<int> result;
int rightEnd = alphabet[0][alphabet[0].size()-1];
int pre = -1;
for(int i=1; i<alphabet.size();i++){
if(alphabet[i][0] > rightEnd){
result.push_back(rightEnd - pre);
pre = rightEnd;
rightEnd = alphabet[i][alphabet[i].size() - 1];
}
else{
rightEnd = max(rightEnd,alphabet[i][alphabet[i].size() - 1]);
}
}
result.push_back(rightEnd - pre);
return result;
}
};
2. 比较巧妙的解法,只关心每个字母最后一次出现的位置。
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> endPos(28);
for(int i = 0; i<s.size(); i++){
endPos[s[i] - 'a'] = i;
}
int curEnd = 0;
int lastEnd = -1;
vector<int> result;
for(int i = 0; i<s.size(); i++){
curEnd = max(curEnd, endPos[s[i] - 'a']);
if(curEnd == i){
result.push_back(curEnd - lastEnd);
lastEnd = curEnd;
}
}
return result;
}
};
56.合并区间
本题相对来说就比较难了。
https://programmercarl.com/0056.%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4.html
Tips:我咋觉得这题还挺简单的,先排个序,然后记录下左右端点,再合并一下就完事了
我的题解:
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) {
sort(intervals.begin(), intervals.end(), cmp);
int begin = intervals[0][0];
int end = intervals[0][1];
vector<vector<int>> result;
for(int i = 1; i < intervals.size(); i++){
if(intervals[i][0] > end){
vector<int> temp;
temp.push_back(begin);
temp.push_back(end);
result.push_back(temp);
begin = intervals[i][0];
end = intervals[i][1];
}
else{
end = max(intervals[i][1], end);
}
}
vector<int> temp;
temp.push_back(begin);
temp.push_back(end);
result.push_back(temp);
return result;
}
};

浙公网安备 33010602011771号