Welcome back to our LeetCode series in TypeScript! Today, we’re tackling a medium-difficulty problem called Merge Intervals. This problem is a favorite in technical interviews, especially at companies like Amazon and Microsoft, because it tests your ability to handle real-world data problems involving ranges or schedules. Whether you’re optimizing meeting times or managing resource allocations, merging overlapping intervals is a practical skill. In this post, we’ll break down the problem, explain it in simple terms, provide a TypeScript solution, and analyze the key concepts.
The Merge Intervals problem is defined as follows: Given an array of intervals
where each interval is represented as [start, end]
, merge all overlapping
intervals and return an array of non-overlapping intervals that cover all the
intervals in the input. For example, if the input is
[[1,3], [2,6], [8,10], [15,18]]
, the output should be
[[1,6], [8,10], [15,18]]
because [1,3]
and [2,6]
overlap and can be merged
into [1,6]
.
This problem is relevant in interviews because it combines multiple fundamental concepts: sorting (to arrange intervals for easier comparison), array manipulation, and logical reasoning about overlaps. It also mimics real-world scenarios like scheduling or resource management, which are common in software engineering tasks. Solving this problem efficiently demonstrates your ability to think algorithmically and handle edge cases.
Imagine you have a bunch of colored strips of paper on a table, and each strip represents a time period, like from 1 o’clock to 3 o’clock. Some strips overlap because they cover the same time, like one from 2 o’clock to 6 o’clock. Your job is to combine the overlapping strips into one big strip so that you have fewer strips, but they still cover all the same times. So, if two strips overlap, you glue them together to make one strip that starts at the earliest time and ends at the latest time of the two.
To do this, first, you line up all the strips from the earliest start time to the latest. Then, you go through them one by one. If the next strip starts before the current one ends, they overlap, so you stretch the current strip to cover the later end time. If they don’t overlap, you keep the current strip as it is and move to the next one as a new strip. At the end, you have a neat set of strips with no overlaps!
Before diving into the code, let’s outline the key concepts and the approach we’ll use to solve this problem:
The time complexity of this solution will be due to the sorting step, where is the number of intervals. The space complexity is or depending on whether the sorting is done in-place or requires additional space, excluding the space needed for the output.
Let’s implement the solution in TypeScript. We’ll define the input as an array
of number arrays (each inner array representing an interval [start, end]
), and
we’ll return a merged array of intervals. Here’s the complete solution with
detailed comments:
function merge(intervals: number[][]): number[][] {
// If the input is empty, return an empty array
if (intervals.length === 0) {
return [];
}
// Sort intervals based on start time
intervals.sort((a, b) => a[0] - b[0]);
// Initialize the result array with the first interval
const merged: number[][] = [intervals[0]];
// Iterate through the sorted intervals starting from the second one
for (let i = 1; i < intervals.length; i++) {
const currentInterval = intervals[i];
const lastMerged = merged[merged.length - 1];
// Check if the current interval overlaps with the last merged interval
if (currentInterval[0] <= lastMerged[1]) {
// If overlap, update the end time of the last merged interval
lastMerged[1] = Math.max(lastMerged[1], currentInterval[1]);
} else {
// If no overlap, add the current interval to the result
merged.push(currentInterval);
}
}
return merged;
}
// Test cases
console.log(
merge([
[1, 3],
[2, 6],
[8, 10],
[15, 18],
]),
); // Output: [[1,6],[8,10],[15,18]]
console.log(
merge([
[1, 4],
[4, 5],
]),
); // Output: [[1,5]]
console.log(
merge([
[1, 4],
[0, 4],
]),
); // Output: [[0,4]]
console.log(
merge([
[1, 4],
[2, 3],
]),
); // Output: [[1,4]]
sort
method.
This ensures we process intervals in ascending order of start times.merged
array as
the baseline.merged
.currentInterval[0] <= lastMerged[1]
), update the
end time of lastMerged
to the maximum of the two end times.merged
.merged
array contains the final non-overlapping intervals.To solidify our understanding, let’s consider a variation or additional test cases that might come up in an interview. Suppose we need to handle unsorted intervals with more complex overlaps or edge cases like fully contained intervals. The solution above already handles these, but let’s explicitly test more scenarios.
function testMergeIntervals(): void {
const testCases = [
{
input: [
[1, 3],
[2, 6],
[8, 10],
[15, 18],
],
expected: [
[1, 6],
[8, 10],
[15, 18],
],
},
{
input: [
[1, 4],
[4, 5],
],
expected: [[1, 5]],
},
{
input: [
[1, 4],
[0, 4],
],
expected: [[0, 4]],
},
{
input: [
[1, 4],
[2, 3],
],
expected: [[1, 4]],
},
{
input: [
[2, 3],
[4, 5],
[6, 7],
[8, 9],
[1, 10],
],
expected: [[1, 10]],
},
{ input: [], expected: [] },
];
testCases.forEach((test, index) => {
const result = merge(test.input);
console.log(`Test ${index + 1}: Input: ${JSON.stringify(test.input)}`);
console.log(`Expected: ${JSON.stringify(test.expected)}`);
console.log(`Got: ${JSON.stringify(result)}`);
console.log(
`Pass: ${JSON.stringify(result) === JSON.stringify(test.expected)}\n`,
);
});
}
// Run the tests
testMergeIntervals();
[[1,10]]
with other intervals inside like
[[2,3],[4,5]]
tests if all smaller intervals are merged into one.[[1,4],[0,4]]
confirms sorting works correctly to merge
intervals regardless of input order.The solution provided earlier handles all these cases correctly because of the sorting step and the overlap check logic.
In this blog post, we’ve explored the Merge Intervals problem, a medium-difficulty LeetCode challenge that’s highly relevant for technical interviews at companies like Amazon and Microsoft. We started with a basic introduction to the problem, which involves combining overlapping time ranges into a simplified set of non-overlapping intervals. Using an ELI5 analogy, we likened it to gluing overlapping paper strips together. We then implemented a solution in TypeScript, leveraging sorting and iterative merging with a time complexity of . We also tested the solution with various edge cases to ensure robustness.
Key takeaways include:
This problem tests fundamental skills in array manipulation, sorting, and logical reasoning—skills that are essential for coding interviews. In the next post, we’ll tackle another exciting LeetCode challenge, building on these concepts. Keep practicing, and happy coding!