1. 问题描述
给定一些整数对(a,b)代表数轴上的两个点,且a<=b,这两个点表示一个线段。请返回最多几个线段重叠在一起。
例如:
- [[1,3],[2,5],[7,9]],因为[1,3]和[2,5]重叠在一起,返回2;
- [[1,2],[2,3]],返回2;
- [[1,2],[2,3],[1,3]],返回3
- [[1,2]],返回1
数组长度至少是1,不超过100_0000;
2. 暴力解法
2.1. 算法分析
我们可以用笔画几条线段发现,任何重叠的部分的左端点,都是某一个线段的左端点。比如[1,3],和[2,4]重叠的部分是[2,3],其中2就是第二个线段的左端点;又比如:[1,5],[3,7],[4,6],重叠的部分是[4,5],其中4就是[4,6]的左端点。既然任何一个重叠的部分都满足这个性质,那么重叠线段最多的那一部分,也满足。
因此我们只需要看一下,哪个左端点下经过的线段数目最多即可。
因此我们可以枚举所有线段的左端点,对于每一个左端点,再次枚举有几条线段经过当前点,返回最大值。
线段[a,b]经过当前点x的充要条件是:a<=x<=b
2.2. 代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 |  public class MaxLineCoincide {
 public static int maxLineCoincide(int[][] lines) {
 int len = lines.length;
 if(len == 1) return 1;
 int ans = 1, t, leftPoint;
 for(int i = 0; i < len; ++i) {
 t=0;
 leftPoint = lines[i][0];
 for(int j=0; j<len; ++j) {
 if(lines[j][0] <= leftPoint && leftPoint <= lines[j][1]) ++t;
 }
 ans = Math.max(t,ans);
 }
 return ans;
 }
 
 public static void main(String[] args) {
 int[][] lines = {
 {1,3},
 {3,4}
 };
 System.out.println(maxLineCoincide(lines));
 }
 
 }
 
 | 
时间复杂度$O(N^2)$
3. 最优解
3.1. 算法分析
上述的解答中,最耗时的部分在于对于每一个左端点,找有几条线段通过这个点?因为给定的线段列表是杂乱的,因此我们只能遍历去找,浪费了时间。
如果我们把所有的线段,按照左端点从小到大排序,那么我们在确定一个左端点有几条线段通过它的时候,就可以排除一些不必要的遍历。
即对于一个左端点leftx,能通过它的线段,只可能是左端点不超过leftx的线段,如果一个线段的左端点都超过了leftx,那么这个线段是不会通过leftx的。
左端点不超过leftx的线段中,不都是满足条件的,只有那些右端点不小于leftx的线段,才是通过leftx的。因此我们还要知道这些线段的所有右端点信息,我们可以把这些线段的右端点都放在小跟堆中,不断从堆顶弹出一个右端点rightx,如果发现一个线段的右端点rightx,小于leftx,那么这个线段是不会计入leftx的答案的,当然在计算leftx的下一个(如果有的话)左端点nextLeftx的时候,更不会计入nextLeftx的答案,因此这个右端点rightx就丢弃了,直到某一个rightx不小于leftx的时候,因为是小跟堆,堆中所有的元素都是不小于leftx的,此时堆的size就是当前leftx的答案。
因此整体的算法如下:
- 把所有的线段,按照左端点从小到大排序
- 设置一个小跟堆,遍历每一个线段,对于每一个线段line:
- 把line的右端点rightx放入小跟堆;
- 只要堆顶元素小于当前线段的左端点leftx,则弹出
- 把堆此时的size作为子问题的答案,和全局答案pk
 
3.2. 代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | import java.util.*;
 public class MaxLineCoincide2 {
 
 public static int maxLineCoincide(int[][] lines) {
 int len = lines.length;
 if(len == 1) return 1;
 int ans = 1;
 Arrays.sort(lines,(a,b)->a[0]-b[0]);
 PriorityQueue<Integer> q = new PriorityQueue<>();
 for(int i = 0; i < len; ++i) {
 q.offer(lines[i][1]);
 while(!q.isEmpty() && q.peek() < lines[i][0]) q.poll();
 ans = Math.max(ans,q.size());
 }
 return ans;
 }
 
 public static void main(String[] args) {
 int[][] lines = {
 {1,3},
 {3,4}
 };
 System.out.println(maxLineCoincide(lines));
 }
 
 }
 
 | 
时间复杂度$O(N\log_2N)$
4. 测试程序
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | import java.util.*;
 public class MaxLineCoincideTest {
 
 public static void main(String[] args) {
 int testTimes = 100_0000;
 int maxArraySize = 100;
 int maxPoint = 10_0000;
 while(testTimes-- > 0) {
 Random r = new Random();
 int size = r.nextInt(maxArraySize) + 1;
 int[][] data = new int[size][2], data2 = new int[size][2];
 for(int i = 0; i < size; ++i) {
 data[i][0] = r.nextInt(maxPoint);
 
 data[i][1] = data[i][0] + r.nextInt(Integer.MAX_VALUE - data[i][0]);
 data2[i][0] = data[i][0];
 data2[i][1] = data[i][1];
 }
 int ans0 = MaxLineCoincide.maxLineCoincide(data);
 int ans1 = MaxLineCoincide2.maxLineCoincide(data2);
 if(ans0 != ans1) {
 System.out.println("ans0: " + ans0 + " ans1: " + ans1);
 for(int i = 0; i < size; ++i) {
 System.out.println(Arrays.toString(data[i]));
 }
 throw new RuntimeException("Oops");
 }
 }
 System.out.println("Done successfully!");
 }
 
 }
 
 |