Acwing 算法基础课——区间系列问题(基于贪心算法的选点
905. 区间选点
该题,翻译一下,就是找到给出的区间中,把有交集的并为一组,已经并一组的集合不能再次合并。
按照贪心去贪,就是将选中的各个区间按照左端点小->大排序,然后标记区间的右端点,当下一个区间的左端点要大于标记的右端点,就要开下一个区间。
对于每个区间的左右端点,
要是该区间的左端点要大于标记的右端点的值的时候,就要新开一个,并且更新该标记的右端点
要是该区间的右端点要于标记的右端点的值的时候,标记的右端点就要更新。
复杂度为O(n);
#include<iostream> #include<algorithm> #include<vector> using namespace std; vector<pair<int,int>>ve; int main() { int n; cin>>n; for(int i=0;i<n;i++){ int l,r; cin>>l>>r; ve.push_back({l,r}); } sort(ve.begin(),ve.end()); int cnt=1; int Or=ve[0].second; //for(int i=0;i<n;i++) cout<<ve[i].first<<" "<<ve[i].second<<endl; for(int i=1;i<n;i++){ int l=ve[i].first; int r=ve[i].second; if(l>Or){ cnt++; Or=r; } else{ if(r<Or) Or=r; } } cout<<cnt; }
908. 最大不相交区间数量
对于该题来说,如题意就是找其中的最大不相交的区间的个数
对于区间的排序,最开始我想的是按照左端点从小到大排序(也就是拿到区间类问题的一般处理数据的想法,记录右端点,要是下一个区间的左端点要比记录的大,
就加进来,并且更新记录的右端点
但是左端点有个弊端:
如该样例,如果按照左端点去排序,然后记下右端点,那么第一个的右端点就覆盖了下面很多个区间,该算法最后的出来只能是1,而实际上是2;
故按照左端点来排列的话,区间的长度会影响最终答案的大小。
当把右端点从小到大排列好后,不论你区间长度多大(也就是说,不论你左边会延长到哪里,都不会影响下一个区间的选择判断;
那么算法就是,把区间按照右端点从小到大排序,标记右端点的值,对于每一个区间,读取左端点,要是左端点要比标记的右端点的值大,就加入进答案,然后更新标记的右端点值
复杂度为O(n);
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<pair<int,int>>v; bool cmp(pair<int,int>a,pair<int,int>b){ return a.second<b.second; } int main() { int n; cin>>n; for(int i=0;i<n;i++){ int l,r; cin>>l>>r; v.push_back({l,r}); } sort(v.begin(),v.end(),cmp); int Or=v[0].second; int cnt=1; for(int i=1;i<n;i++){ if(v[i].first>Or){ cnt++; Or=v[i].second; } } cout<<cnt; }
906. 区间分组
该题就是把区间根据左端点从小到大排列好后,对于区间存入时,找没有重合的区间进行组合,也就是在前面的区间中,找比当前左端点小的右端点,然后加入到该端点中,并且更新该右端点,要是找不到就自己重新开一个,此时要加入当前的右端点。为了贪心,找的应该是前面最小的那个右端点。
暴力的思路,就是去找前面的最小右端点的时候,一个一个去找,是O(n)级别的复杂度。对于本题会超。
若是用优先队列的小根堆,每次在顶端的就是最小的右端点,而当前的左端点只需要判断即可。根堆的复杂度是O(logN),可以降下复杂度;
#include<iostream> #include<algorithm> #include<vector> #include<queue> using namespace std; vector<pair<int,int>>v; int main() { priority_queue<int,vector<int>,greater<int>>q;//小根堆,每次在顶端的是最小的,排序的复杂度为O(logN) int n; cin>>n; for(int i=0;i<n;i++){ int l,r; cin>>l>>r; v.push_back({l,r}); } sort(v.begin(),v.end());//按照左端点从小到大排序 q.push(v[0].second); for(int i=1;i<n;i++){ int r=q.top(); if(r>=v[i].first){ q.push(v[i].second); }else{ q.pop(); q.push(v[i].second); } } cout<<q.size(); }
907. 区间覆盖
对于该题来说,先将需要选择的区间,按照左端点从小到大排序,对于每一个区间,当前的左端点要比需要选择的左端点要小的时候,遍历满足条件的所有区间,并且选出其中右端点最大的,作为下一个需要选择的左端点。
其中的一些细节:
记得对区间进行排序
防止区间选完了,但是其实不够覆盖的情况
防止区间已经完全覆盖了,但是还继续选择的情况。
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<pair<int,int>>v; int main() { int n,s,t; cin>>s>>t; cin>>n; for(int i=0;i<n;i++){ int l,r; cin>>l>>r; v.push_back({l,r}); } sort(v.begin(),v.end()); int Or=s,ans=0,fr; for(int i=0;i<n;i++){ if(v[i].first<=Or){//可以进行选了,选后面左端点小于Or中,右端点最大的 fr=v[i].second; //cout<<fr<<endl; for(int j=i+1;j<n;j++){ if(v[j].first>Or) break; fr=max(fr,v[j].second); i=j; } if(fr>=Or) ans++; Or=fr; } if(Or>=t) break;//要是当前选的区间右端点已经够了,就跳出来,不用继续选。 } if(ans==0||fr<t) cout<<"-1";//fr<t是为了防止一种情况,就是能选的都选完了,但是还是不够 else cout<<ans; }
总的来说,区间问题就是先把区间按照左/右排序,在有序的情况下,对于每一种选择都要做到最优的情况,从而达到贪心的目的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具