重叠不止一次的区间的最大长度子集

Max length subset of intervals which overlaps more than once

我已经为此困惑了一段时间,最后决定看看是否有人能想到一个有效的实现(我不确定是否有)。

给定一系列间隔(例如,以下):

重叠多次的区间的最大子集是多少?在这种情况下,答案将是 [B, C, D],因为 [A, B, C, D] 仅重叠一次,而其他重复重叠的子区间小于 3 个元素。

编辑 9/28:这是一个更复杂的例子:

在这种情况下,答案是 [B, D, E, F, H],因为有多个点从左到右遍历此 table,其中此子集重叠。

另请注意,在这种情况下,答案将是 [A, B, C]。

在每组 n 个间隔的 O(n log n) 中,您可以获得所有相交的子集(参见 Find the maximally intersecting subset of ranges 并添加所需的簿记)。请注意,其中大约有 2n 个,其中 n 是集合中的点数(如果间隔共享起点或终点,则更少)。

我们可以将考虑限制在每组由一个区间相加形成的n组重叠点。这让我们从 2n 下降到 n。我们还可以将我们的考虑限制在元素被删除之前的集合。这可能会进一步减少集合的数量,但不能保证这样做。

现在我们需要找到具有最大交集的那对集合。

天真地这是 O(n^2)。

示例:将此应用于 OP 的示例,我们用以下间隔集结束此答案的第 2 段(使用第二段中的缩减):

I: {{A,B}, {B,C,D}}
II: {{B,A,C,D}}

然后,我们找到 I 的集合与 II 的集合的最大交集:{B,C,D}

下面是我在 Java 中实现的解决方案。这类似于 Dave 的方法 sweep line algorithm.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class IntervalSolver {

     public static void main(String []args){
        ArrayList<Interval> intervals = new ArrayList<>();
        
        final int A = 0;
        final int B = 1;
        final int C = 2;
        final int D = 3;
        
//        intervals.add(new Interval(1, 10, A));
//        intervals.add(new Interval(9, 12, B));
//        intervals.add(new Interval(11, 15, C));
//        intervals.add(new Interval(11, 18, D));
//        intervals.add(new Interval(26, 29, A));
//        intervals.add(new Interval(20, 30, B));
//        intervals.add(new Interval(28, 33, C));
//        intervals.add(new Interval(27, 33, D));
        
//        intervals.add(new Interval(1, 10, A));
//        intervals.add(new Interval(1, 10, B));
//        intervals.add(new Interval(1, 10, C));
//        intervals.add(new Interval(1, 10, D));
//        intervals.add(new Interval(16, 25, A));
//        intervals.add(new Interval(16, 25, B));
//        intervals.add(new Interval(16, 25, C));
        
        intervals.add(new Interval(1, 7, A));
        intervals.add(new Interval(1, 10, B));
        intervals.add(new Interval(1, 10, C));
        intervals.add(new Interval(1, 10, D));
        
        compute(intervals, 4);
     }
     
     public static class Interval {
         public int start;
         public int end;
         public int entity;
         
         public Interval(int start, int end, int entity) {
             this.start = start;
             this.end = end;
             this.entity = entity;
         }
     }
     
     public static class Point implements Comparable<Point> {
         boolean start;
         public Interval interval;
         
         public int pos() {
             return start ? interval.start : interval.end;
         }
         
         public Point(Interval interval, boolean start) {
             this.interval = interval;
             this.start = start;
         }
         
         @Override
         public int compareTo(Point other) {
             int diff = pos() - other.pos();
             if (diff != 0)
                return diff;
             return (start ? 0 : 1) - (other.start ? 0 : 1);
         }
     }
     
     public static class Solution {
         public Interval[] intervals;
         
         public Solution(Interval[] intervals) {
             this.intervals = intervals;
         }
         
         public int value() {
             int value = 0;
             for (int i = 0; i < intervals.length; i++)
                 value += intervals[i] != null ? 1 : 0;
             return value;
         }
         
         @Override
         public boolean equals(Object other) {
             Solution s = (Solution)other;
             if (s.intervals.length != intervals.length)
                 return false;
             
             for (int i = 0; i < intervals.length; i++)
                 if ((intervals[i] == null && s.intervals[i] != null) || (intervals[i] != null && s.intervals[i] == null))
                     return false;
             return true;
         }
         
         @Override
         public int hashCode() {
             int hashCode = 0;
             for (int i = 0; i < intervals.length; i++)
                 hashCode = hashCode << 1 | (intervals[i] != null ? 1 : 0); 
             return hashCode;
         }
         
         @Override
         public String toString() {
             var sb = new StringBuilder();
             sb.append('[');
             for (int i = 0; i < intervals.length; i++) {
                 sb.append(intervals[i] != null ? '1' : '0');
                 if (i < intervals.length - 1)
                     sb.append(',');
             }
             sb.append(']');
             return sb.toString();
         }
     }
     
     public static void compute(List<Interval> series, int entities) {
         ArrayList<Point> points = new ArrayList<>();
         for (var i : series) {
             points.add(new Point(i, true));
             points.add(new Point(i, false));
         }
         points.sort(null);
         
         HashMap<Solution, Integer> solutions = new HashMap<>();
         Interval[] currentIntervals = new Interval[entities];
         
         for (int i = 0; i < points.size(); i++) {
             var p = points.get(i);
             
             if (p.start)
                 currentIntervals[p.interval.entity] = p.interval;
             else
                 currentIntervals[p.interval.entity] = null;
             
             if (i < points.size() - 1 && p.pos() == points.get(i + 1).pos())
                 continue;
             
             var copy = new Solution(currentIntervals.clone());
             int count = solutions.getOrDefault(copy, 0);
             solutions.put(copy, count + 1);
         }
         
         long maxValue = 0;
         Solution best = null;
         for (var entry : solutions.entrySet()) {
             var solution = entry.getKey();
             var count = entry.getValue();
             
             if (count == 1)
                 continue;
             
             long value = solution.value();
             if (value > maxValue) {
                 maxValue = value;
                 best = solution;
             }
         }
         
         // extra intersections:
         for (var entry : solutions.entrySet()) {
             var solution = entry.getKey();
             var count = entry.getValue();
             
             if (count > 1)
                 continue;
             
             long value = solution.value();
             if (value <= maxValue)
                 continue;
             
             for (var innerEntry : solutions.entrySet()) {
                 var innerSolution = innerEntry.getKey();
                 var innerCount = innerEntry.getValue();
                 
                 if (solution == innerSolution || innerCount > 1)
                     continue;
                 
                 long innerValue = innerSolution.value();
                 if (innerValue <= maxValue)
                     continue;
                 
                 var merge = new Solution(solution.intervals.clone());
                 for (int i = 0; i < entities; i++) {
                     var int1 = solution.intervals[i];
                     var int2 = innerSolution.intervals[i];
                     if (int2 == null || int1 == int2)
                         merge.intervals[i] = null;
                 }
                 
                 long mergeValue = merge.value();
                 if (mergeValue > maxValue) {
                     maxValue = mergeValue;
                     best = merge;
                 }
             }
         }         
         
         System.out.println("Best: " + best);
     }
}

它打印出 Best: [false, true, true, true] 表示 BCD。

它可以很容易地扩展以打印交叉点所在的精确坐标。

它的复杂度是 O(n*lg n + nk),其中 n 是间隔数,k 是实体数。如果k = O(n),那么整个复杂度就变成了O(n^2).

编辑: 更改了行为以说明 OP 要求中的更新。这个想法基于采用每对只出现一次的潜在解决方案并将它们相交。根据定义,它们的交集至少出现两次。如果它的值大于当前最好的,它就成为新的结果。复杂度已上升到 O(n*lg n + n^2*k),其中 n 是间隔数,k 是实体数。如果k = O(n),那么整个复杂度就变成了O(n^3).

编辑2: 通过添加同一实体的间隔必须不同的要求,修复了额外交叉点的代码。