今天的贪心算法题目都是区间相关,大都需要排序。涉及到排序,重点是要把cmp()函数的参数设置为引用类型,以及排序对应的时间、空间复杂度。
第1题(LeetCode 435. 无重叠区间)自己AC的思路是按照day 35中第3题(LeetCode 452. 用最少数量的箭引爆气球)的方式统计出不重叠区间数,再用总区间数减去不重叠区间数,就得到需要去除的区间数。与其不同的是,这一题认为两个区间恰好相邻的话,不是重叠区间。
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) { // 要按引用类型传参
return a[0] < b[0]; // 按照end排序也可以
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int end = intervals[0][1];
int cnt = 1;
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] < end) {
end = min(end, intervals[i][1]);
}
else {
cnt++;
end = intervals[i][1];
}
}
return intervals.size() - cnt;
}
};
题解的整体思路与自己的一致,但统计不重复区间数的实现方法不同。首先,排序和遍历方式有2种:
- 按照每个区间的右边界从小到大排序,那么就要按照右边界从小到大,即从左到右的顺序遍历。因为右边界越小,留给之后(右边)区间的空间就越大,就越不容易发生重叠。
- 按照每个区间的左边界从小到大排序,那么就要按照左边界从大到小,即从右到左的顺序遍历。因为左边界越大,留给之后(左边)区间的空间就越大,就越不容易发生重叠。
如果选择第1种,那么就将重叠区间右边界初始化为第一个区间的右边界。在遍历时,遇到区间的左边界大于或等于重叠区间右边界时,就对不重复区间数+1,并更新重叠区间右边界为当前区间的右边界。
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) { // 要按引用类型传参
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int end = intervals[0][1];
int cnt = 1;
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] >= end) {
cnt++;
end = intervals[i][1];
}
}
return intervals.size() - cnt;
}
};
因为使用了sort(),所以空间复杂度至少是O(n)。因为最坏情况下的快速排序(恰好有序)会进行n次递归调用,所以会占用O(n)的栈空间。
二刷:做出来了,但不熟悉。
第2题(LeetCode 763.划分字母区间)自己AC的思路是首先统计每个字母出现的最大下标,然后遍历s,使每一段都尽可能地短。所以,需要预设一个最短段下标,将其设置为第一个字母出现的最大下标,并在遍历时不断根据新字母出现的最大下标,将其更新为更大的值。只要遍历到该最短段下标,就说明段内的所有字母都出现在当前段内,不会出现在之后,于是就可以将当前遍历过的部分当作一段,并开始下一段的遍历。
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> indsMax(26, 0);
for (int i = 0; i < s.size(); ++i) {
indsMax[s[i] - 'a'] = i;
}
vector<int> res;
int indMaxCur = indsMax[s[0] - 'a'], indMaxPre = -1;
for (int i = 0; i < s.size(); ++i) {
indMaxCur = max(indMaxCur, indsMax[s[i] - 'a']);
if (i == indMaxCur) {
res.push_back(indMaxCur - indMaxPre);
indMaxPre = indMaxCur;
}
}
return res;
}
};
需要注意因为结果需要以各段段长度的方式呈现,所以第9行的indMaxPre应该初始化为-1,代表上一个段的段尾下标是-1。
题解的思路和实现方式与自己一样。
二刷:方法一时没想起来,最后做出来了。
第3题(LeetCode 56. 合并区间)自己AC的思路还是首先对区间按照左边界由小到大排序,再做进一步处理。排序后,将首个区间的左边界设置为begin,右边界设置为end,然后开始遍历剩余区间,贪心目标是让当前区间尽可能多地与其他区间合并。如果遇到区间的左边界不大于end,就将其合并,更新end;如果遇到区间的左边界大于end,就将begin和end保存进结果,然后更新begin和end为当前区间。
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
vector<vector<int>> res;
int begin = intervals[0][0], end = intervals[0][1];
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] <= end) {
end = max(end, intervals[i][1]);
}
else {
res.push_back({begin, end});
begin = intervals[i][0];
end = intervals[i][1];
}
}
res.push_back({begin, end});
return res;
}
};
代码中需要注意2个问题:
- 再次注意cmp的参数设置为引用类型,这次就又忘记了,导致时间、空间占用都很高。
- 以及第15行,向vector<vector<int>>添加含2个元素的vector的方式是使用花括号。只有向容器中数据类型为pair的数据结构中添加元素时,才可用pair。比如向vector<pair<int, int>>中添加元素就用pair<int, int>(a, b)。具体可参考day 21中第2题。
另外还要第20行,要把最后一个合并后的区间也添加上。
题解的思路、方法与自己的基本一致,实现方法上有2点不同。第一点是在结果上,并非在区间无法再合并时才将begin和end添加进结果,而是从一开始就添加进去,然后不断的通过.back()取结果vector的末尾元素进行修改;第二点是sort()中的cmp函数参数使用了lambda表达式,这个在之前没有接触过。通过实验,这种方式与定义引用类型参数的cmp函数时间、空间效率基本一致,都比值传递参数的cmp函数效率高很多。