Merge Overlapping Intervals in DSA Go - Time & Space Complexity
We want to understand how the time needed to merge overlapping intervals changes as the number of intervals grows.
How does the program's work increase when we add more intervals?
Analyze the time complexity of the following code snippet.
func mergeIntervals(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
merged := [][]int{}
for _, interval := range intervals {
if len(merged) == 0 || merged[len(merged)-1][1] < interval[0] {
merged = append(merged, interval)
} else {
merged[len(merged)-1][1] = max(merged[len(merged)-1][1], interval[1])
}
}
return merged
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
This code sorts intervals by start time, then merges overlapping ones by comparing each with the last merged interval.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Sorting the intervals and then looping through them once.
- How many times: Sorting takes multiple comparisons depending on number of intervals (n), then the loop runs once for each interval (n times).
As the number of intervals grows, sorting takes more time, and then merging checks each interval once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 log 10 + 10 = 40 operations |
| 100 | About 100 log 100 + 100 = 700 operations |
| 1000 | About 1000 log 1000 + 1000 = 10,000 operations |
Pattern observation: The work grows a bit faster than the number of intervals because sorting is more expensive than just looping.
Time Complexity: O(n log n)
This means the time needed grows a little faster than the number of intervals because sorting takes extra steps, but merging after sorting is just a simple pass.
[X] Wrong: "Merging intervals is just O(n) because we only loop once."
[OK] Correct: Sorting the intervals first takes more time, which dominates the total work, so the overall time is not just linear.
Understanding this time complexity helps you explain how sorting affects your solution and shows you can analyze combined steps clearly.
"What if the intervals were already sorted? How would the time complexity change?"
Practice
[[1,3],[2,6],[8,10]]?Solution
Step 1: Understand overlapping intervals
Intervals overlap if one starts before the other ends. For example, [1,3] and [2,6] overlap because 2 is between 1 and 3.Step 2: Merge overlapping intervals
We combine overlapping intervals into one that covers the full range, like merging [1,3] and [2,6] into [1,6].Final Answer:
Combine intervals that overlap into one continuous interval -> Option CQuick Check:
Merging = Combine overlaps [OK]
- Only sorting by end values
- Removing instead of merging overlaps
- Splitting intervals unnecessarily
Solution
Step 1: Identify sorting by start value
We want to sort intervals by their first element (start), so the comparison should be intervals[i][0] < intervals[j][0].Step 2: Match with Go sort.Slice syntax
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] }) uses sort.Slice with the correct comparison function for start values.Final Answer:
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] }) -> Option AQuick Check:
Sort by start = sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] }) [OK]
- Sorting by end value instead of start
- Using greater than instead of less than
- Mixing start and end indices
package main
import (
"fmt"
"sort"
)
func merge(intervals [][]int) [][]int {
if len(intervals) == 0 {
return intervals
}
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] })
merged := [][]int{intervals[0]}
for i := 1; i < len(intervals); i++ {
last := merged[len(merged)-1]
if intervals[i][0] <= last[1] {
if intervals[i][1] > last[1] {
last[1] = intervals[i][1]
}
} else {
merged = append(merged, intervals[i])
}
}
return merged
}
func main() {
intervals := [][]int{{1,3},{2,4},{5,7},{6,8}}
fmt.Println(merge(intervals))
}Solution
Step 1: Sort intervals by start
Intervals sorted: [[1,3],[2,4],[5,7],[6,8]] (already sorted).Step 2: Merge overlapping intervals
[1,3] and [2,4] overlap, merged to [1,4]. [5,7] and [6,8] overlap, merged to [5,8]. Final merged list: [[1,4],[5,8]].Final Answer:
[[1 4] [5 8]] -> Option DQuick Check:
Merged intervals = [[1 4] [5 8]] [OK]
- Not updating the end value when merging
- Appending intervals without checking overlap
- Incorrect sorting order
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] })
merged := [][]int{}
for _, interval := range intervals {
if len(merged) == 0 || merged[len(merged)-1][1] < interval[0] {
merged = append(merged, interval)
} else {
merged[len(merged)-1][1] = interval[1]
}
}
return merged
}Solution
Step 1: Review merging logic
The code updates the end of last merged interval directly with interval[1] without checking if it's greater.Step 2: Identify missing condition
It should update only if interval[1] > merged[len(merged)-1][1] to avoid shrinking the interval.Final Answer:
Does not check if new interval end is greater before updating -> Option BQuick Check:
Update end only if greater [OK]
- Overwriting end without comparison
- Skipping sorting step
- Appending without overlap check
[[1,4],[4,5],[7,9],[8,10],[12,15]], what is the merged output if intervals that touch at endpoints are considered overlapping?Solution
Step 1: Understand touching intervals as overlapping
Intervals like [1,4] and [4,5] touch at 4, so they merge into [1,5]. Similarly, [7,9] and [8,10] overlap and merge into [7,10].Step 2: Merge all overlapping/touching intervals
After merging, intervals become [[1,5],[7,10],[12,15]].Final Answer:
[[1 5] [7 10] [12 15]] -> Option AQuick Check:
Touching intervals merge = [[1 5] [7 10] [12 15]] [OK]
- Not merging intervals that touch at endpoints
- Leaving touching intervals separate
- Incorrectly merging non-overlapping intervals
Ready to prove it?
Pass the test and this pattern moves unknown
