在面临多个区间的时候,我们经常需要对有交集的区间进行合并这一操作,现在对这一操作进行详细讲解。
为了能更好理解,我们首先熟悉一下我们接下来要使用的一些模板类。
1.为了方便存储每一个区间的左右边界,我们将引入c++中的模板类pair。
typedef pair<int,int> PII;
using PII = pair<int, int>;//这样实现的操作效果一样
我们使用pair模板类关联两个int型的整数,这两个int型整数分别表示区间左边界和右边界。为了方便使用,我们用typedef关键字定义,给它起名为PII。
2.pair方便我们存储一个区间,但我们需要存储多个区间,这时我们需要引入动态数组vector,它可以根据需要增长或缩小,它是C++标准模板库(STL)中的一个容器类,用于存储同一类型的元素的序列。
typedef pair<int,int> PII;
vector<PII> sesg;
我们定义了一个vector
,名为sesg
,它存储的元素类型为PII
,即pair<int, int>
。这里是一个简单的例子,展示了如何向sesg
中添加元素以及如何访问这些元素:
#include <iostream>
#include <vector>
#include <utility> // 包含pair的定义
using namespace std;
typedef pair<int, int> PII; // 定义别名PII
int main() {
vector<PII> sesg; // 声明一个存储PII类型的vector
// 向sesg中添加元素
sesg.push_back(PII(1, 2)); // 使用别名PII添加元素
sesg.push_back({3, 4}); // 使用初始化列表添加元素
sesg.emplace_back(5, 6); // 使用emplace_back原地构造元素
// 遍历sesg并打印元素
for (auto pair : sesg) {
cout << "(" << pair.first << ", " << pair.second << ")" << endl;
}
return 0;
}
好了,理解这些,让我们开始大展伸手吧!
区间和并的关键是如何查找到两个及多个有交集的区间并将其合并,我们先举例五个区间,我简单的表示一下:
五个区间的左右边界分别是:
(1,2) (2,4) (5,6) (7,8) (7,9)
1 2 3 4 5 6 7 8 9
1-2
2---4
5-6
7-8
7---9
按照上图,合并有交集的区间后的区间为(1,4) (5,6) (7,9) 这三个区间。
我们的具体操作为:
- 我们需要做的操作是将每个区间排序,排序的方式是按照左边界大小来排序的,也就是说,(1,2)区间排在(2,4)区间前面,左值小的排在前面。
- 遍历存放区间且排好序的数组sesg,如果遍历到当前区间的右边界大于下一个区间的左边界,则说明这两个区间有交集,合并两个区间,以此直到遍历完每一个区间。
好,接下来是具体的代码实现:
void merge(vector<PII> &sesg) // 定义一个名为merge的函数,接受一个类型为vector<PII>的引用参数sesg
{
vector<PII> res; // 创建一个空的vector<PII>对象res,用于存储合并后的结果
sort(sesg.begin(), sesg.end()); // 对输入的segs进行排序
int st = -2e9, ed = -2e9; // 初始化一个区间用来比较,区间起始点st和结束点ed为-2e9(一个非常小的负数)
for (auto seg : sesg) // 遍历排序后的sesg中的每个区间seg
if (ed < seg.first) // 如果当前结束点ed小于下一个区间的起始点seg.first
{
if (st != -2e9) res.push_back({st, ed}); // 如果起始点st不等于-2e9,将当前的起始点st和结束点ed添加到res中
st = seg.first, ed = seg.second; // 更新起始点st和结束点ed为当前区间的起始点seg.first和结束点seg.second
}
else ed = max(ed, seg.second); // 如果当前区间结束点ed不小于下一个区间的起始点seg.first,则更新结束点ed为当前结束点ed和下一个区间的结束点seg.second中的较大值
if (st != -2e9) res.push_back({st, ed}); // 如果最后一个区间没有被添加到结果向量res中,将其添加进去
sesg = res; // 将结果res赋值给输入参数segs,实现原地修改
}
- 首先对输入的区间列表
segs
进行排序,按照区间的起始点从小到大的顺序排列。 - 初始化起始点
st
和结束点ed
为一个非常小的负数。 - 遍历排序后的区间列表
segs
中的每个区间seg
,判断当前结束点ed
是否小于下一个区间的起始点seg.first
。 - 如果当前结束点
ed
小于下一个区间的起始点seg.first
,说明当前区间已经结束,需要将起始点st
和结束点ed
添加到结果向量res
中,并将起始点st
和结束点ed
更新为下一个区间的起始点seg.first
和结束点seg.second
。 - 如果当前结束点
ed
不小于下一个区间的起始点seg.first
,说明两个区间有重叠部分,只需要更新结束点ed
为当前结束点ed
和下一个区间的结束点seg.second
中的较大值即可。 - 遍历结束后,如果最后一个区间没有被添加到结果向量
res
中,将其添加进去。 - 最后将结果向量
res
赋值给输入参数segs
,实现原地修改。
讲完具体操作,我们该上手做题了
给定 n个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。
输入格式
第一行包含整数 n。
接下来 n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤1000001
−10^9 ≤ li ≤ ri ≤ 10^9
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
希望大家再尝试之后再来理解答案
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;//定义一个数对型的结构
void merge(vector<PII> &sesg)
{
vector<PII> res;//申请一个数对型数组备用
sort(sesg.begin(),sesg.end());//排数数对数组,排序方式是按照数对的左值来排序的
int st=-2e9,ed=-2e9;//定义两个值与每个区间的边界值进行比较
for(auto ses:sesg)//遍历数对型数组sesg
{
if(ses.first>ed)//当遍历的区间的左值大于当前区间的右值,则说明这两个区间没有交集
{
if(st!=-2e9) res.push_back({st,ed});//当前区间不为数对型数组中第一个区间时,将该区间存入备用的数对数组中
st=ses.first,ed=ses.second;//更新当前区间的边界值为遍历区间的边界值
}
else ed=max(ed,ses.second);//遍历的区间的左值小于当前区间的右值,则这两个区间有交集,合并这两个区间,并且将合并区间的右值更新
}
if(st!=-2e9) res.push_back({st,ed});//在上述遍历中,最后一个区间并没有更新到备用数对数组中,所以之一步骤将弥补这一操作
sesg=res;//将备用数组赋值给数对数组sesg
}
int main()
{
int n;
cin>>n;
vector<PII> sesg;//申请一个数对型数组
while(n--)
{
int l,r;
cin>>l>>r;
sesg.push_back({l,r});//将每个区间的边界存入数对数组中
}
merge(sesg);
cout<<sesg.size();
return 0;
}
到这里关于区间合并的操作就讲完了,以上如果有错误欢迎大家指正,本篇内容的思想来自yxc的算法基础课