区间问题是贪心算法常见题目,其类型通常是N×2或者N×Mi的数组,进行一些合并,删除等操作。
(1)合并区间:
(i. 合并区间
(ii. 先统计,再合并区间
(2)计算不重叠区间:
(i. 不重叠区间个数
(ii. 相邻区间视为重叠区间
上面这四种情况,我们用四道题目来解释
原题链接:https://leetcode-cn.com/problems/merge-intervals/
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
解题思路:
1、先将所有的区间按照起始地址的从先到后顺序排序;如果起始地址一样,则按结束地址从后到先排列。
2、比较当前ans[count]的区间和后面的一个区间:
a.如果没有融合,即"当前end<下一个begin",那么当前区间就变成了新的前一个区间,下一个区间成为新的当前区间;
b.如果发生了融合,即"当前end>=下一个begin",更新前一个区间的结束时间。
好,我们来看一下代码:
class Solution {
public:
static bool cmp(const vector<int>&a, const vector<int>&b)
{
if(a[0]==b[0])
return a[1]>b[1];
return a[0]<b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if(intervals.empty())
return intervals;
sort(intervals.begin(), intervals.end(), cmp);
vector<vector<int>>ans;
vector<int>temp;
temp.push_back(intervals[0][0]);
temp.push_back(intervals[0][1]);
ans.push_back(temp);
int count=0; //记录结果是第几个
for(int i=1; i<intervals.size(); i++)
{
if(ans[count][1] >= intervals[i][0])
{
if(ans[count][1]<=intervals[i][1])
{
ans[count][1]=intervals[i][1];
}
}
else
{
temp[0]=intervals[i][0];
temp[1]=intervals[i][1];
ans.push_back(temp);
count++;
}
}
return ans;
}
};
原题链接:https://leetcode-cn.com/problems/partition-labels/
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”, “defegde”, “hijhklij”。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。
提示:
S的长度在[1, 500]之间。
S只包含小写字母 ‘a’ 到 ‘z’ 。
解题思路:
1、申请一个长度为26的vector,用于存储该字母出现的最后位置,初始值为-1;
2、使用i遍历S,将上面的vector的第(S[i]-'a)的值更新为 i,然后一直循环更新下去;
3、遍历区间[begin, end],(循环前,begin初始为0,end初始为letters[0]),遍历同时更新end,看是否要扩大这个区间(贪心算法,如果end能更新就更新)
4、将[start, end]这个区间的长度加入到结果集ans。同时更新start = end + 1
重复1-4,直到i遍历完整个S,ans即结果。
好,我们来看一下代码:
class Solution {
public:
vector<int> partitionLabels(string S) {
vector<int>ans;
vector<int>letters(26, -1);//初始化26字母
for(int i=0; i<S.length(); i++)
{
int index= S[i]-'a';
letters[index]=i;
}
int begins=0, ends=0;
for(int i=0; i<S.length(); i++)
{
int index= S[i]-'a';
ends=max(ends, letters[index]);
if(i == ends)
{
ans.push_back(ends- begins+1);
begins= ends+1;
}
}
return ans;
}
};
原题链接: https://leetcode-cn.com/problems/non-overlapping-intervals/
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
解题思路:
1、和上面一样,将intervals的右区间从小到大排列,如果右区间相同,则左区间从小到大排列;
2、如果当前的起点,比下一个的终点还要小,count++;
好,一起来看一下代码吧:
class Solution {
public:
static bool cmp(vector<int>&a, vector<int>b)
{
if(a[1]==b[1])
{
return a[0]<b[0];
}
return a[1]<b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.empty()) return 0;
//将intervals的右区间从小到大排列
//如果右区间相同,则左区间从小到大排列
sort(intervals.begin(), intervals.end(), cmp);
int count=0, pre=intervals[0][1];
for(int i=1; i<intervals.size(); i++)
{
//如果相互重叠了
if(intervals[i][0]< pre)
{
count++;
}
else
{
pre= intervals[i][1];
}
}
return count;
}
};
原题链接: https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example:
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:
2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
解题思路:
这道题我感觉有点难,所以咧,就借用Leetcode上面题解的图片来解释:
很明显我们使用两支箭就能使全部气球引爆,我们借助此例子来思考如何用贪心算法的思想来计算结果。
让我们根据气球的结束坐标进行排序,然后一个个进行检查。第一个气球是标有 0 的绿色气球,它的结束坐标是 6。
其他的气球有两种情况:
开始坐标小于 6,例如红色气球,它可以与 0 气球一起被一支箭引爆。
开始坐标大于 6,例如黄色气球,它不可以与 0 气球被一支箭引爆,因此需要增加箭的数量。
这代表了我们可以跟踪气球的结束坐标,若下个气球开始坐标在当前气球的结束坐标前,则我们可以用一支箭一起引爆;若下个气球的开始坐标在当前气球的结束坐标后,则我们必须增加箭的数量。并跟踪下个气球的结束坐标。
然后,解题步骤和上面差不多:
1、先将points按右边界从小到大排列;如果右边界相等,则从左边界从小到大排列;
2、用贪心算法循环,如果当前右边界,小于第 i 个的左边界,那么count++;更新当前的右边界为第 i 个的右边界。
好,一起看一下代码吧:
class Solution {
public:
static bool cmp(vector<int>&a, vector<int>b)
{
if(a[1]==b[1]) return a[0]<b[0];
return a[1]<b[1];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(points.empty()) return 0;
sort(points.begin(), points.end(), cmp);
int count=0;
int cur=0;
for(int i=0; i<points.size(); i++)
{
if(points[cur][1]< points[i][0])
{
count++;
cur=i;
}
}
//易错点:因为最后一组,并没有比他大来比较
//所以进不了if的那个条件,count的实际结果比当前结果多1
count++;
return count;
}
};
下一篇更新: 贪心算法(背包问题)