Поиск элементарных интервалов в перекрывающихся интервалах

я наткнулся на хороший вопрос при подготовке к интервью Программирование.

учитывая набор возможно перекрывающихся интервалов, вам нужно написать функцию, чтобы вернуть все элементарные интервалы между ними. Например: если вам заданы интервалы в виде следующего списка пар: {{1,5}, {3,10}, {5,11}, {15,18}, {16,20}}, затем нужно вернуть следующее:

{{1,3}, {3,5}, {5,10}, {10,11}, {15,16}, {16,18}, {18,20}}

обратите внимание на следующее в приведенном выше ответе:

  • интервал {11,15} опущен в ответе, потому что он не существует на входе.
  • интервал {1,5} от входа был разделен на {1,3}, {3,5} в ответе из-за начальной точки "3", определенной в {3,10} в вход, который разрезает интервал на два элементарных интервала.

подпись метода в Java:

List<Pair<Integer, Integer>> generateElementaryIntervals(List<Pair<Integer, Integer> intervals)

одним из решений, которые я себе представлял, было разделение ввод в непересекающиеся множества, а затем простая сортировка O(NlogN) по всем числам в каждом непересекающемся множестве даст ответ. Существует ли более эффективный способ сделать это?

3 ответов


вы можете сначала разбить эту проблему на вложенные интервалы, а затем заниматься каждым вложением отдельно. Под вложенными я подразумеваю интервалы, которые разделяют хотя бы одну точку. Для пример:

{{1,5}, {3,10}, {5,11}, {15,18}, {16,20}}

есть два вложения:

{1,5}, {3,10}, {5,11}

и

{15,18}, {16,20}

в общем, чтобы определить вложения, вы можете сортировать интервалы на основе левой конечной точки (как в вашем примере), а затем запускать и запускать новое вложение всякий раз, когда вы видите {x,y}, {x',y'} С y < x'.

для вложенности "элементарные интервалы" формируются отсортированной последовательностью (без повторов) значений. В Примере гнезда дают

(1,3,5,10,11) -> {1,3}, {3,5}, {5,10}, {10,11}

и

(15,16,18,20) -> {15,16}, {16,18}, {18,20}

таким образом, общий алгоритм может выглядеть следующим образом:

  • сортировка интервалов на основе левой конечной точки
  • запуск через интервалы до {x,y}, {x',y'} С y < x'
  • С начала {x,y}, сделать упорядоченный список конечных точек (без повторов), скажи a0,a1,...,ak
  • добавить элементарные интервалы {ai,a(i+1)} на i = 0...k-1
  • удалить интервалы до {x,y} и продолжить с шага 2

вы можете отсортировать конечные точки, а затем выполнить итерацию по порядку. Чтобы узнать, входите вы или нет, вы можете сохранить количество интервалов, которые охватывают каждую точку. Левый конец интервала вносит +1, в то время как правый вносит -1: (Обратите внимание, что я использую TreeMap, который сортируется)

static class Pair<T, K> {
    public Pair(T first, K second){
        this.first = first;
        this.second = second;
    }

    public String toString(){
        return "(" + first + ", " + second + ")";
    }

    T first;
    K second;   
}    

static List<Pair<Integer, Integer>> generateElementaryIntervals(List<Pair<Integer, Integer>> intervals) {
    TreeMap<Integer, Integer> overlaps = new TreeMap<Integer, Integer>();
    for(Pair<Integer, Integer> interval : intervals){
        int value = overlaps.containsKey(interval.first) ? overlaps.get(interval.first)+1 : 1;
        overlaps.put(interval.first, value);

        value = overlaps.containsKey(interval.second) ? overlaps.get(interval.second)-1 : -1;
        overlaps.put(interval.second, value);
    }

    List<Pair<Integer, Integer>>  retValue = new ArrayList<Pair<Integer,Integer>>();

    int overlap = 0;
    boolean in = false;
    int last = 0;
    for(int point : overlaps.keySet()){
        if(in)
            retValue.add(new Pair(last, point));

        overlap += overlaps.get(point);
        last = point;
        in = overlap > 0;
    }

    return retValue;
}    

public static void main(String[] args) {

    List<Pair<Integer, Integer>> l = new ArrayList<Pair<Integer, Integer>>();
    l.add(new Pair<Integer, Integer>(1,5));
    l.add(new Pair<Integer, Integer>(3,10));
    l.add(new Pair<Integer, Integer>(5,11));
    l.add(new Pair<Integer, Integer>(15,18));
    l.add(new Pair<Integer, Integer>(16,20));

    for(Object o : generateElementaryIntervals(l)){
        System.out.println(o.toString());
    }

}

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

каждый элемент будет хранить два значения:number, и является ли это первым или вторым номером (от входа).

эти пары затем будут отсортированы, сначала по его внутреннему number, а затем по его положению (second пошел бы раньше first)

распечатать список интервалами, печатается каждое число вместе со следующим номером, со следующими правилами:

  • вы не будете печатать повторяющиеся числа (например, вы не будете печатать 5,5)
  • если у вас есть только second количество, а затем first number, вы не будете печатать этот элементарный интервал, так как в этом диапазоне нет значений.