问题描述
我已经对此感到困惑了一段时间,最后决定看看是否有人可以想到一种有效的实现方式(我不确定是否有这样的实现方式。)
给出一系列间隔(例如,以下内容):
重叠多次的最大间隔子集是什么?在这种情况下,答案是[B,C,D],因为[A,B,C,D]仅重叠一次,而其他重复重叠的子间隔小于3个元素。
编辑9/28:这是一个更复杂的示例:
在这种情况下,答案是[B,D,E,F,H],因为有多个点从左到右遍历此表,并且该子集重叠。
请注意,在这种情况下,答案将是[A,B,C]。
解决方法
下面是我用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,D));
// intervals.add(new Interval(1,A));
// intervals.add(new Interval(1,B));
// intervals.add(new Interval(1,C));
// intervals.add(new Interval(1,D));
// intervals.add(new Interval(16,25,A));
// intervals.add(new Interval(16,B));
// intervals.add(new Interval(16,C));
intervals.add(new Interval(1,7,A));
intervals.add(new Interval(1,B));
intervals.add(new Interval(1,C));
intervals.add(new Interval(1,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]
,表示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)
。
Edit2: 通过添加相同实体的间隔必须不同的要求,修复了额外交集的代码。
,在每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,D}}
然后,我们找到I中的集合与II中的集合的最大交集:{B,C,D}