#алгоритм #любой_язык
На входе есть список отрезков какой-то (непрерывной) оси (числовой или временной, не важно), каждый отрезок представлен парой (упорядоченных) координат — начало и конец (эти точки также принадлежат отрезку). Необходимо слить все пересекающиеся отрезки и получить на выходе список отрезков не имеющих пересечений (но этот новый список не должен иметь точки, которых не было в отрезках на входе). Например, имеем на входе: [1; 5] [2; 4] [7; 9] [3; 6] тогда, на выходе необходимо получить: [1; 6] // здесь слиты [1; 5], [2; 4], [3; 6] [7; 9] реализовал алгоритм примерно такой: function merge(segments) { while (true) { const newSegments = []; for (const x of segments) { let found = false; for (const y of newSegments) { if (x.end >= y.start && x.start <= y.end) { y.start = Math.min(y.start, x.start); y.end = Math.max(y.end, x.end); found = true; break; } } if (!found) newSegments.push(x); } if (segments.length == newSegments.length) break; segments = newSegments; } return segments; } function print(segments) { console.log(segments.map(s => `[${s.start}; ${s.end}]`).join(' ')); } let segments = [ { start: 1, end: 5 }, { start: 2, end: 4 }, { start: 7, end: 9 }, { start: 3, end: 6 } ]; print(segments); print(merge(segments)); Он работает, но имеет сложность O(n³). Можно ли написать решение лучше? Есть идея: отсортировать входной список и как-то адаптировать метод сканирующей прямой; но как это реализовать пока не могу придумать.
Ответы
Ответ 1
Сортировка по левой точке - это правильный первый шаг, получаем: [1; 5] [2; 4] [3; 6] [7; 9] Далее сравниваем первый отрезок со вторым - начало второго попадает в первый отрезок, значит сливаем отрезки: началом будет начало первого, а концом - max(конец первого, конец второго), получаем [1; 5]. [1; 5] [3; 6] [7; 9] Так же сливаем [1; 5] и [3; 6] в [1; 6] [1; 6] [7; 9] [1; 6] и [7; 9] не пересекаются, значит фиксируем [1; 6] с ним уже никакой другой отрезок не пересечется, и повторяем все действия начиная с [7; 9] сложность сортировки O(n log(n)), сложность слияния O(n) итого O(n log(n))Ответ 2
Получилось в итоге так: function merge(segments) { if (segments.length === 0) return []; const inp = segments.map(s => ({...s})); inp.sort((a, b) => a.start - b.start); const out = []; let s = inp[0]; for (let i = 1; i < inp.length; ++i) { if (inp[i].start <= s.end) { s.end = Math.max(s.end, inp[i].end); } else { out.push(s); s = inp[i]; } } out.push(s); return out; } function print(segments) { console.log(segments.map(s => `[${s.start}; ${s.end}]`).join(' ')); } let segments = [ { start: 1, end: 5 }, { start: 2, end: 4 }, { start: 7, end: 9 }, { start: 3, end: 6 } ]; print(segments); print(merge(segments));Ответ 3
больше от скуки запилил решение на C# public class Solution { public int[][] Merge(int[][] intervals) { if (intervals == null || intervals.Length == 0) return new int[0][]; Array.Sort(intervals, 0, intervals.Length, new IntervalComparer()); var stack = new Stack(); var curr = intervals[0]; for(int i=1; i = next[0]) curr = new int[] {curr[0], Math.Max(curr[1], next[1])}; else { stack.Push(curr); curr = next; } } stack.Push(curr); var ret = new int[stack.Count][]; for(int i = ret.Length-1; i>=0; i--) ret[i] = stack.Pop(); return ret; } private class IntervalComparer : IComparer { public int Compare(int[] x, int[] y) { return x[0].CompareTo(y[0]); } } } Этот код побил 99.7% решений этой задачи на leetcode (решений на C#) Ответ 4
const merge = ss => { ss = ss.sort((a, b) => a.start - b.start) let c = ss.shift() const m = [c] ss.forEach(s => (s.start <= c.end) ? (s.end > c.end) && (c.end = s.end) : m.push(c = s)) return m } console.log(merge([ { start: 1, end: 5 }, { start: 2, end: 4 }, { start: 7, end: 9 }, { start: 3, end: 6 } ]))
Комментариев нет:
Отправить комментарий