Возможный вопрос для интервью: как найти все перекрывающиеся интервалы - PullRequest
63 голосов
/ 28 декабря 2010

Это не вопрос для собеседования как таковой , как я наткнулся на это в своем проекте, но я подумал, что это может быть приличный вопрос.

У вас есть N пар интервалов, скажем, целых чисел. Вы должны идентифицировать все интервалы, которые перекрываются друг с другом за O (N) время. Например, если у вас есть

{1, 3} {12, 14} {2, 4} {13, 15} {5, 10}

ответ: {1, 3}, {12, 14}, {2, 4}, {13, 15}. Обратите внимание, что вам не нужно группировать их, поэтому результат может быть в любом порядке, как в примере.

Я только что добавил O (N) время, потому что алгоритм KMP принимает O (N) для поиска строки. : D

Лучшее, что я придумал и что я сейчас использую в проекте, это O (N ^ 2). Да, грубая сила довольно печальна, но никто не жалуется, поэтому я не буду рефакторинг. : P Тем не менее, мне было любопытно, есть ли у большего ума более элегантное решение.

Ответы [ 11 ]

94 голосов
/ 28 декабря 2010

Бросьте конечные точки интервалов в массив, пометив их как начальные или конечные точки. Сортируйте их, разрывая связи, помещая конечные точки перед начальными точками, если интервалы закрыты, или наоборот, если они наполовину открыты.

1S, 2S, 3E, 4E, 5S, 10E, 12S, 13S, 14E, 15E

Затем выполните итерацию по списку, отслеживая, сколько интервалов мы находимся (это соответствует количеству обработанных начальных точек минус количество конечных точек). Всякий раз, когда мы достигаем начальной точки, когда мы уже находимся в интервале, это означает, что у нас должны быть перекрывающиеся интервалы.

1S, 2S, 3E, 4E, 5S, 10E, 12S, 13S, 14E, 15E
    ^                          ^
   overlap                    overlap

Мы можем определить, с какими интервалами перекрываться, сохраняя данные рядом с конечными точками и отслеживая, в каких интервалах мы находимся.

Это решение O (N logN) с сортировкой, являющейся основным фактором.

31 голосов
/ 19 марта 2012

Сортировка интервалов по начальной точке.Затем сверните этот список, объединяя каждый интервал со своим соседом (то есть (1,4), (2,6) -> (1,6)), если они перекрываются.Результирующий список содержит эти интервалы без перекрывающегося партнера.Отфильтруйте их из исходного списка.

Это линейно по времени после начальной операции сортировки, которая может быть выполнена любым (n log n) алгоритмом.Не уверен, как ты справишься с этим.Даже операция «отфильтровывать дубликаты» в конце линейна, если вы используете уже отсортированный порядок ввода и вывода первой операции.

Вот реализация на Haskell:

-- Given a list of intervals, select those which overlap with at least one other inteval in the set.
import Data.List

type Interval = (Integer, Integer)

overlap (a1,b1)(a2,b2) | b1 < a2 = False
                       | b2 < a1 = False
                       | otherwise = True

mergeIntervals (a1,b1)(a2,b2) = (min a1 a2, max b1 b2)

sortIntervals::[Interval]->[Interval]
sortIntervals = sortBy (\(a1,b1)(a2,b2)->(compare a1 a2))

sortedDifference::[Interval]->[Interval]->[Interval]
sortedDifference [] _ = []
sortedDifference x [] = x
sortedDifference (x:xs)(y:ys) | x == y = sortedDifference xs ys
                              | x < y  = x:(sortedDifference xs (y:ys))
                              | y < x  = sortedDifference (x:xs) ys

groupIntervals::[Interval]->[Interval]
groupIntervals = foldr couldCombine []
  where couldCombine next [] = [next]
        couldCombine next (x:xs) | overlap next x = (mergeIntervals x next):xs
                                 | otherwise = next:x:xs

findOverlapped::[Interval]->[Interval]
findOverlapped intervals = sortedDifference sorted (groupIntervals sorted)
  where sorted = sortIntervals intervals

sample = [(1,3),(12,14),(2,4),(13,15),(5,10)]
8 голосов
/ 28 декабря 2010

Стандартный подход к задачам intervales-on-the-line состоит в том, чтобы отсортировать их по начальной точке, а затем просто идти от первого к последнему.O(n*logn) (O(n), если уже отсортировано)

end = 0;
for (current in intervals) {
    if current.start < end {
        // there's an intersection!
        // 'current' intersects with some interval before it
        ...
    }
    end = max(end, current.end)
}
5 голосов
/ 28 декабря 2010

Не уверен насчет O (N), но что, если мы сначала отсортируем их по первому числу в каждом кортеже, а затем последовательно найдем те, у которых первое число кортежа больше, чем у наибольшего числа из предыдущих кортежей,которые также не перекрываются со следующим кортежем.

Таким образом, вы сначала получите:

{1, 3}, {2,4}, {5, 10}, {12, 14}, {13, 15}

, так как 4 (самое большое) <5 и 10 <12, {5, 10} изолированы. </p>

Это будет означать, что мы отслеживаем наибольшее число, с которым мы сталкиваемся, и каждый раз, когда мы находим кортежчей начальный номер больше, мы проверяем, не перекрывается ли он со следующим.

Тогда это становится зависимым от эффективности алгоритма сортировки, потому что последний процесс будет O (N)

2 голосов
/ 20 февраля 2011

Предположим, что разница между начальной и конечной точкой мала, скажем, <32. Например. 1..32. Тогда каждый интервал может быть записан как битовый шаблон в 32-битном слове. например, <code>[1, 2] -> 001; [2, 3]-> 010; [1, 3] -> 011; [2, 3, 4] -> 110. Два интервала или комбинации интервалов перекрываются, если их побитовое значение AND не равно нулю. например. [1,2] перекрывается [1,3], потому что 001&011 == 001, не ноль. O (n) alg - сохранить побитовое ИЛИ интервалов, видимых до сих пор, и AND каждый новый:

bitsSoFar = 0
for (n=0; n < bitPatterns.length; n++)
    if (bitPatterns[n] & bitsSoFar != 0)
        // overlap of bitPatterns[n] with an earlier pattern
        bitsSoFar |= bitPatterns[n]

Оставлено как упражнение:

  • изменить алгоритм, чтобы также идентифицировать перекрытие битовой комбинации с более поздней

  • отработать битовую комбинацию для интервала в O (1)

1 голос
/ 22 марта 2014

Эта проблема может быть сведена к проблеме уникальности элемента.

Уникальность элемента имеет нижнюю границу омега (n log n) (подсчет количества сравнений), поэтому вы не можете добиться большего успеха, чем это.

1 голос
/ 19 марта 2012

Если N пар интервалов - целые числа, то мы можем получить его за O (n).

Сортировка по первому номеру в паре, а затем по второму номеру. Если все являются целыми числами, мы можем использовать сортировку по сегментам или сортировку по основанию, чтобы получить ее по O (n).

{1, 3}, {2,4}, {5, 10}, {12, 14}, {13, 15}

Затем объедините один за другим,

{1,3}

{1,4} с перекрытием {1,3} и {2,4}

{1,4}, {5,10}

{1,4}, {5,10}, {12,14}

{1,4}, {5,10}, {12,15} с перекрытием {12,14} и {13,15}

Комбинация займет время O (N)

0 голосов
/ 03 августа 2018

Вот реализация O(N lg N) в Java, которая расширяет ответ, предоставленный @Nikita Rybak.

Мое решение находит каждый интервал, который перекрывается хотя бы с одним другим интервалом, и считает их оба как перекрывающиеся интервалы.Например, два интервала (1, 3) и (2, 4) из исходного вопроса ОП перекрывают друг друга, и поэтому в этом случае имеются 2 перекрывающихся интервала.Другими словами, если интервал A перекрывается с интервалом B, то я добавляю и A и B к результирующему набору интервалов, которые перекрываются.

Теперь рассмотрим интервалы (1, 100), (10, 20) и (30, 50).Мой код обнаружит, что:

[ 10,  20] overlaps with [  1, 100]
[ 30,  50] overlaps with [  1, 100]

Resulting intervals that overlap with at least one other interval:
[  1, 100]
[ 30,  50]
[ 10,  20]

Чтобы предотвратить двойной подсчет (1, 100), я использую Java Set, в котором хранятся только уникальные объекты Interval.

Мое решение следуетэтот контур.

  1. Сортировка всех интервалов по начальной точке.Этот шаг O(N lg N).
  2. Отслеживать intervalWithLatestEnd, интервал с последней конечной точкой.
  3. Итерировать по всем интервалам в отсортированном списке.Если интервал перекрывается с intervalWithLatestEnd, добавьте оба к набору.Обновите intervalWithLatestEnd при необходимости.Это шаг O(N).
  4. Возвращение набора (и преобразование в список при необходимости).

Общее время работы O(N lg N).Требуется выходной набор размера O(N).

Реализация

Чтобы добавить интервалы в набор, я создал собственный класс Interval, который переопределяет equals(), как и ожидалось.

class Interval {
    int start;
    int end;
    Interval(int s, int e) { 
        start = s; end = e; 
    }

    @Override
    public String toString() {
        return String.format("[%3d, %3d]", start, end);
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + start;
        result = prime * result + end;
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Interval other = (Interval) obj;
        if (start != other.start)
            return false;
        if (end != other.end)
            return false;
        return true;
    }
}

А вот код, который запускает алгоритм:

private static List<Interval> findIntervalsThatOverlap(List<Interval> intervals) {

    // Keeps unique intervals.
    Set<Interval> set = new HashSet<Interval>();

    // Sort the intervals by starting time.
    Collections.sort(intervals, (x, y) -> Integer.compare(x.start, y.start));

    // Keep track of the interval that has the latest end time.
    Interval intervalWithLatestEnd = null;

    for (Interval interval : intervals) {

        if (intervalWithLatestEnd != null &&
            interval.start < intervalWithLatestEnd.end) {

            // Overlap occurred.
            // Add the current interval and the interval it overlapped with.
            set.add(interval); 
            set.add(intervalWithLatestEnd);

            System.out.println(interval + " overlaps with " +
                               intervalWithLatestEnd);
        }

        // Update the interval with latest end.
        if (intervalWithLatestEnd == null ||
            intervalWithLatestEnd.end < interval.end) {

            intervalWithLatestEnd = interval;
        }
    }
    // Convert the Set to a List.
    return new ArrayList<Interval>(set);
}

Контрольные примеры

Вот контрольный пример, в котором выполняются исходные интервалы OP:

public static void testcase() {

    List<Interval> intervals = null;
    List<Interval> result = null;

    intervals = new ArrayList<Interval>();

    intervals.add(new Interval(1, 3));
    intervals.add(new Interval(12, 14));
    intervals.add(new Interval(2, 4));
    intervals.add(new Interval(13, 15));
    intervals.add(new Interval(5, 10));


    result = findIntervalsThatOverlap(intervals);
    System.out.println("Intervals that overlap with at least one other interval:");
    for (Interval interval : result) {
        System.out.println(interval);
    }
}

с результатом:

[  2,   4] overlaps with [  1,   3]
[ 13,  15] overlaps with [ 12,  14]
Intervals that overlap with at least one other interval:
[  2,   4]
[  1,   3]
[ 13,  15]
[ 12,  14]

Наконец, вот более сложный тестовый пример:

public static void testcase() {

    List<Interval> intervals = null;
    List<Interval> result = null;

    intervals = new ArrayList<Interval>();

    intervals.add(new Interval(1, 4));
    intervals.add(new Interval(2, 3));
    intervals.add(new Interval(5, 7));
    intervals.add(new Interval(10, 20));
    intervals.add(new Interval(15, 22));
    intervals.add(new Interval(9, 11));
    intervals.add(new Interval(8, 25));
    intervals.add(new Interval(50, 100));
    intervals.add(new Interval(60, 70));
    intervals.add(new Interval(80, 90));


    result = findIntervalsThatOverlap(intervals);
    System.out.println("Intervals that overlap with at least one other interval:");
    for (Interval interval : result) {
        System.out.println(interval);
    }
}

с результатом:

[  2,   3] overlaps with [  1,   4]
[  9,  11] overlaps with [  8,  25]
[ 10,  20] overlaps with [  8,  25]
[ 15,  22] overlaps with [  8,  25]
[ 60,  70] overlaps with [ 50, 100]
[ 80,  90] overlaps with [ 50, 100]
Intervals that overlap with at least one other interval:
[  2,   3]
[  8,  25]
[  9,  11]
[ 50, 100]
[  1,   4]
[ 15,  22]
[ 10,  20]
[ 60,  70]
[ 80,  90]
0 голосов
/ 11 мая 2014

Это простая O(N*log(N)) реализация в Python:

def overlapping(intervals):
    last = (-1, -1)
    overlapping = set()

    for curr in sorted(intervals, key=lambda p: p[0]):
        if curr[0] < last[1]:
            overlapping.add(curr)
            overlapping.add(last)
        last = max(curr, last, key=lambda p: p[1])

    return list(overlapping - set((-1, -1)))

print overlapping([(1, 3), (12, 14), (2, 4), (13, 15), (5, 10)])
#=> [(1, 3), (13, 15), (2, 4), (12, 14)]

Во-первых, он сортирует интервалы по времени окончания, затем для каждого интервала сравнивает начальное время с наибольшим найденным временем окончания, а если оно меньше, это означает, что имеется перекрытие.

Сортировочная часть - это та, которая требует больше всего времени, поэтому итоговая сложность составляет N*log(N).

0 голосов
/ 28 декабря 2010

Прошло довольно много времени с тех пор, как я его использовал, но решение, которое я использовал, было производным от красно-черного дерева, описанного во введении к алгоритмам, называемого интервальным деревом. Это дерево, отсортированное по началу интервала, поэтому вы можете быстро (двоичный поиск) сначала найти первый подходящий узел. IIRC, узлы были упорядочены по свойству, которое позволяет вам прекратить "ходить" по дереву, когда узлы-кандидаты не могут соответствовать вашему интервалу. Так что я думаю, что это был O (m) поиск, где m - это число совпадающих интервалов.

Я нашел эта реализация .

Brett

[править] Перечитывая вопрос, это не то, что вы спросили. Я думаю, что это лучшая реализация, когда у вас есть список (например) встреч, уже запланированных в конференц-залах (которые добавляются в дерево), и вы хотите найти, какие комнаты все еще доступны для встречи с новым началом и продолжительностью. (поисковый термин). Надеемся, что это решение имеет какое-то значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...