Card 03/ 08
The Merge Template in Python, Java and TypeScript
Sort by start, keep a list of merged intervals, and either extend the last one or append a new one.
The merge template sorts by start and makes one pass that either extends the last merged interval or appends a new one. The highlighted lines are the three that change between interval problems: the sort key, the overlap test and the extension. Each listing merges [4, 7], [1, 3], [8, 11], [2, 5] and [12, 13].
The merge template in three languages
def merge(intervals: list[list[int]]) -> list[list[int]]:
ordered = sorted(intervals, key=lambda interval: interval[0])
merged: list[list[int]] = []
for start, end in ordered:
if merged and start <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], end)
else:
merged.append([start, end])
return merged
intervals = [[4, 7], [1, 3], [8, 11], [2, 5], [12, 13]]
print("input: ", intervals)
print("merged:", merge(intervals))input: [[4, 7], [1, 3], [8, 11], [2, 5], [12, 13]]
merged: [[1, 7], [8, 11], [12, 13]]import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class Merge {
static List<int[]> merge(int[][] intervals) {
int[][] ordered = intervals.clone();
Arrays.sort(ordered, Comparator.comparingInt(interval -> interval[0]));
List<int[]> merged = new ArrayList<>();
for (int[] interval : ordered) {
int[] last = merged.isEmpty() ? null : merged.get(merged.size() - 1);
if (last != null && interval[0] <= last[1]) {
last[1] = Math.max(last[1], interval[1]);
} else {
merged.add(new int[] {interval[0], interval[1]});
}
}
return merged;
}
static String show(List<int[]> intervals) {
StringBuilder text = new StringBuilder();
for (int[] interval : intervals) {
if (text.length() > 0) {
text.append(", ");
}
text.append(Arrays.toString(interval));
}
return "[" + text + "]";
}
public static void main(String[] args) {
int[][] intervals = {{4, 7}, {1, 3}, {8, 11}, {2, 5}, {12, 13}};
System.out.println("input: " + show(Arrays.asList(intervals)));
System.out.println("merged: " + show(merge(intervals)));
}
}input: [[4, 7], [1, 3], [8, 11], [2, 5], [12, 13]]
merged: [[1, 7], [8, 11], [12, 13]]type Interval = [number, number];
function merge(intervals: Interval[]): Interval[] {
const ordered = [...intervals].sort((x, y) => x[0] - y[0]);
const merged: Interval[] = [];
for (const [start, end] of ordered) {
const last = merged[merged.length - 1];
if (last !== undefined && start <= last[1]) {
last[1] = Math.max(last[1], end);
} else {
merged.push([start, end]);
}
}
return merged;
}
const show = (intervals: Interval[]): string =>
"[" + intervals.map(([start, end]) => `[${start}, ${end}]`).join(", ") + "]";
const intervals: Interval[] = [[4, 7], [1, 3], [8, 11], [2, 5], [12, 13]];
console.log("input: " + show(intervals));
console.log("merged: " + show(merge(intervals)));input: [[4, 7], [1, 3], [8, 11], [2, 5], [12, 13]]
merged: [[1, 7], [8, 11], [12, 13]]All three print [[1, 7], [8, 11], [12, 13]]. The extension takes the larger of the two ends rather than the current end, because a short interval can sit inside a long one: [2, 12] followed by [6, 7] must stay [2, 12]. Java sorts with Comparator.comparingInt, not (x, y) -> x[0] - y[0], which can overflow and misorder very large or very negative starts; TypeScript's subtraction is safe because its numbers are doubles. Each version copies before sorting, so the caller's list keeps its order. The template is O(n log n) time and O(n) space for the output.
← → move through sections and cards