LeetCode 986. Interval List Intersections

[Med] LeetCode 986. Interval List Intersections

链接: https://leetcode.com/problems/interval-list-intersections/

题目描述:
Given two lists of closed intervals, each list of intervals is pairwise disjoint and in sorted order.

Return the intersection of these two interval lists.

(Formally, a closed interval [a, b] (with a <= b) denotes the set of real numbers x with a <= x <= b. The intersection of two closed intervals is a set of real numbers that is either empty, or can be represented as a closed interval. For example, the intersection of [1, 3] and [2, 4] is [2, 3].)

Example 1:

Input:
A = [[0,2],[5,10],[13,23],[24,25]],
B = [[1,5],[8,12],[15,24],[25,26]]
Output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
Reminder: The inputs and the desired output are lists of
Interval objects, and not arrays or lists.

Note:

  • 0 <= A.length < 1000
  • 0 <= B.length < 1000
  • 0 <= A[i].start, A[i].end, B[i].start, B[i].end < 10^9

Tag: Pre-Sum
解题思路
对于所有的A片段来说,总共会出现以下三种情况。相反,B也可能出现下面三种情况(在这里就没有重复的写)

A: |---------|
B:   |----|

A: |---------|
B:          |--------|

A: |------|
B:             |-----|

解法一:
我们需要两个指针,pa和pb来遍历两个数组。对于以下的三种情况,我们分情况讨论。
对于情况1来说,我们首先记录B的范围,因为B完全被A所包括。在这之后,我们应该将B的指针加一。
因为B的下一位指针还有可能和A[pa]的区间有所重合。
对于情况2来说,我们首先将B的起始位置和A的结束位置作为一个新的范围记录下来。在这之后我们应该
将A的指针往下移动一位,因为B的结束点在这两个线段靠后的位置,所以可能和A的下一位匹配。
对于情况3来说,我们应该移动的是A的坐标,因为这样才有可能和B的线段区间匹配。
综上所述,谁区间结束的早,我们应该将起移动到下一位

class Solution {
    public int[][] intervalIntersection(int[][] A, int[][] B) {
        if(A.length == 0 || B.length==0) return new int[0][0];
    	List<int[]> list = new ArrayList<>();

    	int pa = 0, pb = 0;
    	while(pa<A.length && pb < B.length){
            
    		int[] ta = A[pa];
    		int[] tb = B[pb];
    		if(ta[0] >= tb[0] && ta[0] <= tb[1]){
    			if(ta[1] <= tb[1]){
    				list.add(ta);
                    pa++;
    			}else{
    				list.add(new int[]{ta[0], tb[1]});
                    pb++;
    			}
    		}else if(tb[0]>= ta[0] && tb[0]<=ta[1]){
    			if(tb[1] <= ta[1]){
    				list.add(tb);
                    pb++;
    			}else{
    				list.add(new int[]{tb[0], ta[1]});
                    pa++;
    			}
    		}else if(ta[0]>tb[1]) pb++;
    		else if(tb[0] > ta[1]) pa++;
    	}
        

        int[][] res = new int[list.size()][2];
    	for(int i=0; i< list.size(); i++) res[i] = list.get(i);
        return res;
    }
}

解法二:
另一种写法

class Solution {
    public int[][] intervalIntersection(int[][] A, int[][] B) {
        int numA = 0;
        int numB = 0;
        int start = 0;
        int end = 0;
        ArrayList<int[]> list = new ArrayList<>();
        while (numA < A.length && numB < B.length){
            start = Math.max(A[numA][0], B[numB][0]);
            if (A[numA][1] <= B[numB][1]){
                end = A[numA][1];
                if (end >= start){
                    list.add(new int[]{start, end});
                }
                numA++;
            }
            else{
                end = B[numB][1];
                if (end >= start){
                    list.add(new int[]{start, end});
                }
                numB++;
            }
        }
        int[][] result = new int[list.size()][];
        for (int i =0; i < result.length; i++){
            result[i] = list.get(i);
        }
        return result;
       
    }
}

解法三:
因为这两个数组内的区间互相不交叉,我们还可以用扫描线的做法来做这道题目。
扫描线的经典套路就是首先将每个区间的起始位置标注为1,结束位置标志为-1。然后我们用一个整数代表扫描过所有区间的线。碰到区间的起始位置的时候加一,结束位置减一。这样这个整数就可以代表了重合的区间段有多少。在这道题目里面我们也首先这样子将A数组和B数组当中的所有元素都加入到我们的list当中,每一个新的元素我们用一个简单的数组来表示,数组的第一位是这个元素出现的位置,第二位代表了这个元素是起始点还是终点。我们遍历A和B两个数组之后首先将所有的元素放入List当中,然后需要将新的list按照位置排个序。

我们需要三个变量,第一个变量count代表的是当前的扫描线,也就是累积起来的重合区间的个数。prevCount代表的是在遇到当前线段(头/尾)的前一个重合的区间个数。prev代表的是上一个线段(头/尾)的位置。

我们观察例子中的模式,只有两种情况下区间会被加入到结果中。
第一种是,prevCount = 0且 count = 1,并且前一位的结尾(prev)正好等于当前位置pos[0]的情况。代表的是前一个区间的结尾和当前区间的开头正好重合的情况。

A: |------|
B:         |-----|

或者:

B:         |-----|
A: |------|

第二种情况就是prevCount = 2且count= 1的情况,代表的是有两段区间重合,并且此时其中某一段区间已经结尾的情况。
可以是:

A: |---------|
B:   |----|

或者

A: |---------|
B:          |--------|

所有的情况都不外乎这两种情况。所以每一次遇到这两种情况其中之一我们就可以将[prev, pos[0]]这一段区间加入到结果中了。

这一个做法的缺点就是在给所有元素排序的时候需要O(NLOGN)的时间复杂度,当然,这个可以通过两个指针顺序遍历两个数组优化到O(N)的时间复杂度

class Solution {
    public int[][] intervalIntersection(int[][] A, int[][] B) {
        if(A.length == 0 || B.length == 0) return new int[][]{};
        List<int[]> list = new ArrayList<>(), res = new ArrayList<>();
        for(int[] a : A){
            list.add(new int[]{a[0], 1});
            list.add(new int[]{a[1], -1});
        }
        
        for(int[] b : B){
            list.add(new int[]{b[0], 1});
            list.add(new int[]{b[1], -1});
        }
        
        Collections.sort(list, (a,b) -> a[0]-b[0]);
        int count =0, prev =-1, prevCount=0;

        for(int[] pos : list){
            prevCount = count;
            count+=pos[1];

            if(prevCount == 0 && count == 1){
                if(pos[0]-prev > 0) continue;
                res.add(new int[]{prev, pos[0]});
            }else if(prevCount == 2 && count ==1){
                res.add(new int[]{prev, pos[0]});
            }
            prev = pos[0];
        }
     
        int[][] anw = new int[res.size()][2];
        for(int i=0; i< res.size(); i++) anw[i] = res.get(i);
        return anw;
      
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值