1.无重叠区间:
自己的思路是一旦发现重叠的两个数组就把两个数组的右边界都变成两个数组中最短的右边界,这样就相当于“删掉”了:
其实如果是上一个数组右边界大,其实是不用变的,因为接下来遍历的数组已经和他没关系了,不会和他比较了,虽然在模拟的时候感觉好像不大对,但是仔细一想按照题目的意思是一旦发现有重叠的话就是把右边界大的数组“删掉“了,这么一想没问题,毕竟这个变右边界的操作就是模拟删除数组的操作。对照下面图的操作就是把[1,4]给删除了。
贪心算法:
他是右边界排序,然后从左往右遍历(其实我的思路就是左边界排序,直接记录重叠区间的数量)(右边界排序是记录非重叠的区间的数量最大数量,用总区间数量去减,就是重叠区间的最小数量)
class Solution {
public:
// 按照区间右边界排序
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int count = 1; // 记录非交叉区间的个数
int end = intervals[0][1]; // 记录区间分割点
for (int i = 1; i < intervals.size(); i++) {
if (end <= intervals[i][0]) {
end = intervals[i][1];
count++;
}
}
return intervals.size() - count;
}
};
记录非交叉区间的个数(这是右边界排序好了的,所以当有重合区间时不用做多余的操作,因为end始终是有重叠的区间中第一个数组的右边界,也就是在重叠的数组中最小的右边界),如图:
注:
这里非交叉区间为什么要初始化为1,我知道初始化为1,要问要是来三个一样的一维数组不是没有非交叉区间吗?我的理解是这个非交叉区间就是不要删除的区间,当题目是三个一样的一维数组时,这个初始化为1就表示第一个一维数组不用删,剩下两个数组都要删。
右边界排序,直接记录重叠区间数量也可以,但是左区间排序,记录非重叠的区间的数量最大数量,用总区间数量去减,是不行的:
1.
2.
2.划分字母区间:
我的思路是:1.找出每个字母的最大范围(其实我就是这个不会找)
2.然后遍历第一个字母的最大区间,左闭右闭,如果出现字母的右边界超过第一个字母的右边界,那就更新右边界,遍历的最大值也要更新,直到里面的字母的右边界都小于最终的右边界 ,输出最终的右边界的下标。
3.把2中右边界的下一位当作第一个字母重复2的操作,直到遍历完数组
题解(诶嘿,和我的想法差不多):
在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。
可以分为如下两步:
- 统计每一个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
如图:
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;
}
};
当然题解的思路比我的巧妙在它不需要字母的范围只需要字母范围的最远右边界就行了,这样就好找了。
注:
1.vector初始化和普通数组的初始化:
vector与普通数组的初始化
vector<int> hard(27,0);
int hard[27]={0};
都是定义一个数组大小为27,初始化都为0的数组,当然一个是动态数组一个是普通的数组
对于动态数组,就是可以不用关心初始时候的大小,可以随意往里放数据。
2.
关于这里要好好理一理:
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;
}
}
首先要记住right是在遍历过程中字母的最大右边界所以right每次都要和自己还有遍历的字母的最大右边界比较,取最大值
然后当i,也就是遍历的下标=right的时候可以把前面和后面划开了。
最后推入result的是区间长度不是right的下标,因为left是变的,也就是划分区间的左边界是变的。
还有一种思路,这里代码就不补充了,详情请见代码随想录:
统计字符串中所有字符的起始和结束位置,记录这些区间(实际上也就是435.无重叠区间 (opens new window)题目里的输入),将区间按左边界从小到大排序,找到边界将区间划分成组,互不重叠。找到的边界就是答案。
3.合并区间:
想想很简单,但是对于合并这个操作怎么写呢?是重新定义一个数组还是用earse删除字母呢,如果用earse删除元素,这样会使二维数组中的一维数组前移,后面还怎么遍历?如果重新定义一个数组,万一有3个一维数组有重合区间:
1 3,2 6, 5 10,这样先输入一个1 6,后面就要输入一个2 10,但是应该输入的是1 10啊
如何去模拟合并区间呢?
其实就是用合并区间后左边界和右边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把原区间加入到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;
}
};
他是先放进去一个再在原数组的下一个和result里面最后一个进行比较,其实我的思路再想多一点应该能想到的。
虽然不用删除操作,但是可以复习一下数组上的删除操作:
注:
if (result.back()[1] >= intervals[i][0]) { // 发现重叠区间
// 合并区间,只更新右边界就好,因为result.back()的左边界一定是最小值,因为我们按照左边界排序的
result.back()[1] = max(result.back()[1], intervals[i][1]);
}
这里result里面的是左边界小的,而且是对result里面的数组进行操作,也就是说改变的是左边界小的数组的右边界,而不是左边界大的,也就是intervals上的数组的左边界,这里注意左边界小的右边界不一定就小,所以要加一个max 。