有1,2,3....n个数组,每个数组包含一系列一维线段的表示,每个数组的元素结构为(point,length)(point>=0 且 length>=1,都为整数),表示从point开始长为length的线段,现将n个数组中的线段合并,其中需要考虑数组的优先级:1>2>....>n,高优先级的数组的线段将覆盖并切分重叠的低优先级数组线段。求合并后的数组?
示例:
1数组:(0,2),(6,9)
2数组:(1,3),(7,10)
合并后:(0,2),(2,2),(6,9),(15,2)
输入:
n(数组个数)
依次输入n个数组。
输出:
合并后的数组。
如果题目的描述和示例出现了冲突或者BUG,应该怎么回答系列。
其实题目的描述和示例是没问题的。
@Shendu.cc: 谢谢,确实是我看错了。
测试结果:
我的方法是,n个数组,从第n个开始,从后往前一个一个合并。
使用set ,方便插入和自动排序已经二分查找,插入一个节点会自动排序,插入的效率O(log n)
set中存放的是最终结果,整体来看时间效率是O(n*m*logn*2) m代表平均每个数组的长度
在插入set的时候,要注意区间的交合,所以条件挺多的。
还没有测大量数据,可能会有bug
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <map> #include <algorithm> #include <set> using namespace std; struct Node { int point; int length; int end; bool operator <(const Node &r) const { if(point == r.point) return end<r.end; else return point<r.point; } Node(){} Node(int point,int end) { this->point=point; this->end=end; this->length = end-point+1; } }a[105][105],res[1005]; set<Node> ans; bool sort(Node a,Node b) { if(a.point==b.point) return a.end<b.end; else return a.point<b.point; } int b[105]; int n; int main() { printf("请输入数组的个数\n"); scanf("%d",&n); for(int i=0;i<n;i++) { printf("请输入第%d个数组的个数\n",i+1); scanf("%d",&b[i]); printf("请依次输入point,length\n"); for(int j=0;j<b[i];j++) { scanf("%d%d",&a[i][j].point,&a[i][j].length); a[i][j].end=a[i][j].point+a[i][j].length-1; } } for(int i=n-1;i>=0;i--) { for(int j=0;j<b[i];j++) { if(i==n-1) { ans.insert(Node(a[i][j].point,a[i][j].end)); continue; } //返回集合中第一个大于等于当前段的元素,注意集合中是没有相交的段 set<Node>::iterator x = ans.lower_bound(a[i][j]); //如果当前端比集合中的都小 if(x==ans.begin()) { Node Max = *x;//第一个大于等于当前段的元素 if(a[i][j].end>=Max.end) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); } else if(a[i][j].end<Max.end&&a[i][j].end<=Max.point) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); ans.insert(Node(a[i][j].end+1,Max.end)); } else if(a[i][j].end<Max.point) { ans.insert(Node(a[i][j].point,a[i][j].end)); } continue; } //如果当前段比集合中的都大 else if(x==ans.end()) { ans.insert(Node(a[i][j].point,a[i][j].end)); continue; } Node Max = *x;//第一个大于等于当前段的元素 x--; Node Min = *x;//第一个小于当前段的元素 //下面进入插入set操作 if(a[i][j].point<Min.end&&a[i][j].point>=Min.point) { ans.erase(Min); ans.insert(Node(Min.point,a[i][j].point)); if(a[i][j].end>=Max.end) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); } else if(a[i][j].end>=Max.point&&a[i][j].end<Max.end) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); ans.insert(Node(a[i][j].end+1,Max.end)); } else if(a[i][j].end<Max.point&&a[i][j].end>=Min.end) { ans.insert(Node(a[i][j].point,a[i][j].end)); } else if(a[i][j].end<Min.end) { ans.insert(Node(a[i][j].point,a[i][j].end)); ans.insert(Node(a[i][j].end+1,Min.end)); } } else if(a[i][j].point>=Min.end&&a[i][j].point<=Max.end) { if(a[i][j].end>=Max.end) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); } else if(a[i][j].end<Max.end&&a[i][j].end>=Max.point) { ans.erase(Max); ans.insert(Node(a[i][j].point,a[i][j].end)); ans.insert(Node(a[i][j].end+1,Max.end)); } else if(a[i][j].end<Max.point) { ans.erase(Node(a[i][j].point,a[i][j].end)); } } } } printf("结果是:\n"); set<Node>::iterator ite1 = ans.begin(); set<Node>::iterator ite2 = ans.end(); for(;ite1!=ite2;ite1++) { printf("(%d,%d)\n",(*ite1).point,(*ite1).length); } return 0; }
是否考虑这样一个情况:
如:高优先级a存在(12,10)
b存在(8,6),(17,10)
即a一个线段会重合b多段线段。
@luoyin500: 跑出来的结果是
(8,5)
(12,10)
(22,5)
@luoyin500: 你好,经过思考,发现使用set是有问题的。于是我用离散化和暴力重新写了,其实也不是暴力时间效率也不错。你可以出几组测试数据。
// // main.cpp // test2 // // Created by 小康 on 28/03/2018. // Copyright © 2018 小康. All rights reserved. // #include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <map> #include <algorithm> #include <set> using namespace std; struct Node { int point; int length; int end; bool operator <(const Node &r) const { if(point == r.point) return end<r.end; else return point<r.point; } Node(){} Node(int point,int end) { this->point=point; this->end=end; this->length = end-point+1; } }a[105][105],res[1005]; set<Node> ans; bool sort(Node a,Node b) { if(a.point==b.point) return a.end<b.end; else return a.point<b.point; } int b[105]; int n; map<int,int> m; int c[10005]; int main() { printf("请输入数组的个数\n"); scanf("%d",&n); int p=1; for(int i=0;i<n;i++) { printf("请输入第%d个数组的个数\n",i+1); scanf("%d",&b[i]); printf("请依次输入point,length\n"); for(int j=0;j<b[i];j++) { scanf("%d%d",&a[i][j].point,&a[i][j].length); a[i][j].end=a[i][j].point+a[i][j].length-1; c[++p]=a[i][j].point; c[++p]=a[i][j].end; } } sort(c+1,c+p+1); for(int i=1;i<=p;i++) { m[c[i]]=i; } int tagl[10005]; int tagr[10005]; int tag[10005]; memset(tagl,0,sizeof(tagl)); memset(tagr,0,sizeof(tagr)); memset(tag,0,sizeof(tag)); int q=1; for(int i=n-1;i>=0;i--) { for(int j=0;j<b[i];j++) { int l=m[a[i][j].point]; int r=m[a[i][j].end]; for(int k=l;k<r;k++) { if(tagl[k]!=0) { if(tagl[k]>r) { tagl[r]=tagl[k]; tagr[tagl[k]]=r; tagl[k]=0; } else { tagr[tagl[k]]=0; tagl[k]=0; } } if(tagr[k]!=0) { if(tagr[k]<l) { tagl[tagr[k]]=l; tagr[l]=tagr[k]; tagr[k]=0; } else { tagl[tagr[k]]=0; tagr[k]=0; } } } tagl[l]=r; tagr[r]=l; } } printf("结果是:\n"); int pre=0; for(int i=1;i<=p;i++) { if(tagl[i]!=0) { if(i==pre&&pre!=0) printf("(%d,%d)\n",c[i]+1,c[tagl[i]]-c[i]); else printf("(%d,%d)\n",c[i],c[tagl[i]]-c[i]+1); pre=tagl[i]; } } return 0; }
中午想了想这道题。
这道题是一个典型的范围问题,这种问题在排时间表时很常见。但是跟排时间表不同的是,这道题要取差集,也就是题中提到的分割。 虽然俗话说的好,没有什么需求不是N个if解决不了的,有的话就加几个for。按自然思维套路就会建一个线段类,然后建个二位数组开始一个一个线段的比较。
但是这道题有个可以偷懒的方法,我们可以用空间换取时间。将最后输出的数组想象成一个1XN的格子,然后传入的线就是一条要涂的颜色。然后倒叙涂抹这条线,随后按照不同颜色输出数组,就是结果。
即使这道题出现小数,让所有线条的所有值×10,让他们放大,如果还有小数继续放大,直到放大到没有小数。当然这个方法一旦出现小数,就会成倍的增大内存开销。需要注意
犯懒用C# 写的,没有像上面两个大神用C++,凑合看
using System; using System.Collections.Generic; namespace CoreConsoleTest.AliLine { public class Main { public static void run(string[] args) { List<List<Line>> lines = new List<List<Line>> { new List<Line> { new Line { Point = 0, Length = 2 }, new Line { Point = 6, Length = 9 } }, new List<Line>{ new Line { Point = 1, Length = 3 }, new Line { Point = 7, Length = 10 } } }; int enlarge = 0; //检查放大倍数.如果全都是整数,这个段Foreach可以注释掉 foreach (var array in lines) { foreach (var line in array) { if (line.Point.ToString().IndexOf('.') > -1) { int len = line.Point.ToString().Length - line.Point.ToString().IndexOf('.') - 1; if (len > enlarge) enlarge = len; } if (line.Length.ToString().IndexOf('.') > -1) { int len = line.Length.ToString().Length - line.Length.ToString().IndexOf('.') - 1; if (len > enlarge) enlarge = len; } } } enlarge = (int)Math.Pow(10, enlarge); int color = 0; List<int> ColorLine = new List<int>(); for (int i = lines.Count - 1; i >= 0; i--) { foreach (var line in lines[i]) { //当填充线不够长,增加 for (int a = ColorLine.Count; a < line.End * enlarge; a++) ColorLine.Add(-1); for (int p = (int)(line.Point * enlarge); p < line.End * enlarge; p++) ColorLine[p] = color; color++; } } Console.WriteLine("输出:"); int point = ColorLine[0], count = 1, start = 0; for (int i = 1; i < ColorLine.Count; i++) { if (point == ColorLine[i]) { count++; } else { if (point != -1) Console.Write($"({(double)start / enlarge},{(double)count / enlarge}) "); start = i; count = 1; point = ColorLine[i]; } } Console.WriteLine($"({(double)start / enlarge},{(double)count / enlarge}) "); } private class Line { public double Point { get; set; } public double Length { get; set; } public double End { get { return Point + Length; } } } } }
测试小数:
修改测试数据
List<List<Line>> lines = new List<List<Line>> { new List<Line> { new Line { Point = 0, Length = 2.12 }, new Line { Point = 6, Length = 9 } }, new List<Line>{ new Line { Point = 1, Length = 3 }, new Line { Point = 7, Length = 10 } } };
如果测试数据的区间是(1,10000000)这样子,每次染色要循环一千万次,如果n大一点,就跑炸了。
@Shendu.cc: 大神指点的没错,这个方法就是简单粗暴,我上面也说了,这个方法会导致内存暴增。这个是毋庸置疑的。
这个也就是为了得出题目要求结果的一个偷懒的解决方案。
用到生产中这个方法肯定是不可取的,至少会考虑多线程。
关于区间比较的做法我也做了,但是感觉这个简单粗暴的写法更有意思。
@写代码的相声演员: 你可以做一些小动作,把暴增的内存和暴走的时间都规避掉。不过这也无关紧要了。感兴趣可以研究。
@写代码的相声演员: 测试平台上有OJ限制的。。。。
@luoyin500: 敢问OJ是啥?
要求什么语言?
– 写代码的相声演员 6年前