文章目录
- 56. 合并区间
- 题目描述
- 思路
- 贪心算法
- 方法一:直接在res中修改
- 代码逻辑梳理:
- 方法二:在原数组中插入一个超出题目范围的数组
- 代码逻辑梳理:
56. 合并区间
题目描述
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
- 1 <= intervals.length <= 104
- intervals[i].length == 2
- 0 <= starti <= endi <= 104
思路
本题的本质其实还是判断重叠区间问题。
大家如果认真做题的话,话发现和我们刚刚讲过的452. 用最少数量的箭引爆气球和 435. 无重叠区间都是一个套路。
这几道题都是判断区间重叠,区别就是判断区间重叠后的逻辑,本题是判断区间重贴后要进行区间合并。
所以一样的套路,先排序,让所有的相邻区间尽可能的重叠在一起,按左边界,或者右边界排序都可以,处理逻辑稍有不同。
按照左边界从小到大排序之后,如果 intervals[i][0] <= intervals[i - 1][1]
即intervals[i]
的左边界 <= intervals[i - 1]
的右边界,则一定有重叠。(本题相邻区间也算重贴,所以是<=)
这么说有点抽象,看图:(注意图中区间都是按照左边界排序之后了)
知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢?
其实就是用合并区间后左边界和右边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把原区间加入到result数组。
贪心算法
方法一:直接在res中修改
这段代码是C++语言编写的,用于解决合并重叠区间的问题。下面是对这段代码的详细注释:
// 定义Solution类
class Solution {
public:
// 静态比较函数,用于sort函数中,按区间的起始位置升序排序
// 如果a的起始位置小于b的起始位置,则返回true
static bool cmp(vector<int> a, vector<int> b) {
return a[0] < b[0];
}
// 合并区间的主要功能函数
vector<vector<int>> merge(vector<vector<int>>& intervals) {
// 存储最终合并后的区间数组
vector<vector<int>> res;
// 首先,根据区间的起始位置,使用自定义的比较函数cmp对区间进行排序
sort(intervals.begin(), intervals.end(), cmp);
// 将排序后的第一个区间添加到结果数组中,作为合并的起点
res.push_back(intervals[0]);
// 从第二个区间开始遍历排序后的区间数组
for(int i = 1; i < intervals.size(); i++) {
// 如果当前区间的起始位置小于等于结果数组中最后一个区间的结束位置,
// 则说明当前区间与结果数组中的最后一个区间有重叠
if(intervals[i][0] <= res.back()[1])
// 更新结果数组中最后一个区间的结束位置为当前区间和最后一个区间结束位置的较大值
// 以此合并重叠的区间
res.back()[1] = max(res.back()[1], intervals[i][1]);
else
// 如果当前区间与结果数组中的最后一个区间没有重叠,
// 则直接将当前区间添加到结果数组中
res.push_back(intervals[i]);
}
// 返回合并后的区间数组
return res;
}
};
代码逻辑梳理:
-
排序:首先通过
sort
函数和自定义的比较函数cmp
对输入的区间数组进行排序,确保区间是按照起始位置升序排列的。这是解决问题的关键步骤,因为只有排序后,我们才能通过顺序遍历来识别哪些区间是重叠的。 -
初始化:将排序后的第一个区间直接添加到结果数组
res
中。这是因为,不管后面的区间是否与它重叠,第一个区间总是需要被包含在最终的结果中。 -
遍历合并:从第二个区间开始遍历,对每个区间,检查它的起始位置是否小于等于结果数组中最后一个区间的结束位置。如果是,说明当前区间与结果数组中的最后一个区间重叠,此时需要更新结果数组中最后一个区间的结束位置为两个区间结束位置的较大值,以此来合并区间。如果不重叠,直接将当前区间添加到结果数组中。
-
返回结果:经过遍历和条件判断后,所有重叠的区间都被合并,函数返回最终的结果数组。
这种方法的优点是逻辑清晰,且只需要一次遍历就可以完成合并,效率较高。
方法二:在原数组中插入一个超出题目范围的数组
这段代码是使用C++语言编写的,用于解决合并区间的问题。下面是这段代码的详细注释:
// 定义Solution类
class Solution {
public:
// 静态比较函数,用于sort函数中,按区间的起始位置升序排序
// 如果a的起始位置小于b的起始位置,则返回true
static bool cmp(vector<int> a,vector<int> b)
{
return a[0]<b[0];
}
// 合并区间的主要函数
vector<vector<int>> merge(vector<vector<int>>& intervals) {
// 存储最终合并后的区间结果
vector<vector<int>> res;
// 首先,根据区间的起始位置,使用自定义的比较函数cmp对区间进行排序
sort(intervals.begin(),intervals.end(),cmp);
// 为了处理方便,在intervals的末尾添加一个超出范围的区间[10001,10001],
// 这样在遍历时就不需要特殊处理最后一个区间
intervals.push_back({10001,10001});
// 遍历排序后的区间
for(int i=0;i<intervals.size()-1;i++)
{
// 如果当前区间的结束位置不小于下一个区间的起始位置,说明两个区间有重叠
if(intervals[i+1][0]<=intervals[i][1])
{
// 更新下一个区间的结束位置为当前区间和下一个区间结束位置的较大值
// 这样做是为了合并重叠的区间
intervals[i+1][1]=max(intervals[i+1][1],intervals[i][1]);
// 更新下一个区间的起始位置为当前区间的起始位置
intervals[i+1][0]=intervals[i][0];
}
else
{
// 如果没有重叠,则将当前区间加入到结果中
res.push_back(intervals[i]);
}
}
// 返回合并后的区间集合
return res;
}
};
代码逻辑梳理:
-
排序:首先,通过自定义的排序函数
cmp
将所有区间根据起始位置进行升序排序。这是合并区间问题的核心步骤之一,它可以让我们在后续遍历时更容易判断区间是否重叠。 -
遍历:遍历排序后的区间数组。对于每个区间,检查它与下一个区间是否重叠(即当前区间的结束位置不小于下一个区间的起始位置)。
-
合并处理:如果两个区间重叠,更新下一个区间的起始和结束位置,将它们合并为一个区间。这里通过修改下一个区间的起始和结束位置来实现合并,而非直接操作当前区间,是为了简化逻辑,因为通过前面的排序保证了遍历时区间是按起始位置顺序来的。
-
添加非重叠区间:如果两个区间不重叠,将当前区间加入到结果集中。
-
处理边界:通过在
intervals
末尾添加一个超出范围的区间[10001,10001],简化了最后一个区间的处理逻辑,避免了在循环中额外判断是否为最后一个区间。这个超出范围的区间不会被加入最终结果,因为它是在遍历结束前添加的,仅用作逻辑上的辅助。
这种方法直接在原数组上进行操作,减少了空间复杂度,但修改了输入数组,这在某些情况下可能不是最佳做法。在实际应用中,根据是否允许修改输入数组来选择合适的方法。