Count Ways to Group Overlapping Ranges
Problem
You are given a 2D integer array ranges where ranges[i] = [starti, endi]
denotes that all integers between starti and endi (both inclusive) are contained in the ith range.
You are to split ranges into two (possibly empty) groups such that:
- Each range belongs to exactly one group.
- Any two overlapping ranges must belong to the same group.
Two ranges are said to be overlapping if there exists at least one integer that is present in both ranges.
- For example,
[1, 3]and[2, 5]are overlapping because2and3occur in both ranges.
Return thetotal number of ways to split ranges into two groups.
Since the answer may be very large, return it modulo 10^9 + 7.
Examples
Example 1
Input: ranges = [[6,10],[5,15]]
Output: 2
Explanation:
The two ranges are overlapping, so they must be in the same group.
Thus, there are two possible ways:
- Put both the ranges together in group 1.
- Put both the ranges together in group 2.
Example 2
Input: ranges = [[1,3],[10,20],[2,5],[4,8]]
Output: 4
Explanation:
Ranges [1,3], and [2,5] are overlapping. So, they must be in the same group.
Again, ranges [2,5] and [4,8] are also overlapping. So, they must also be in the same group.
Thus, there are four possible ways to group them:
- All the ranges in group 1.
- All the ranges in group 2.
- Ranges [1,3], [2,5], and [4,8] in group 1 and [10,20] in group 2.
- Ranges [1,3], [2,5], and [4,8] in group 2 and [10,20] in group 1.
Constraints
1 <= ranges.length <= 10^5ranges[i].length == 20 <= starti <= endi <= 10^9
Solution
Method 1 – Connected Components (Merging Intervals)
Intuition
The key idea is to treat each group of overlapping ranges as a connected component. Any two overlapping ranges must be in the same group, so the number of ways to split the ranges is 2^c, where c is the number of connected components (groups of overlapping intervals). Each group can be assigned to either of the two groups independently.
Approach
- Sort the ranges by their start value.
- Initialize a counter
cfor connected components and a variable to track the end of the current group. - Iterate through the sorted ranges:
- If the current range starts after the end of the current group, increment
c(new component). - Otherwise, merge the current range into the current group by updating the end.
- If the current range starts after the end of the current group, increment
- The answer is
2^cmodulo10^9 + 7.
Code
C++
class Solution {
public:
int countWays(vector<vector<int>>& ranges) {
const int MOD = 1e9 + 7;
sort(ranges.begin(), ranges.end());
int c = 0, end = -1;
for (auto& r : ranges) {
if (r[0] > end) {
++c;
end = r[1];
} else {
end = max(end, r[1]);
}
}
long long ans = 1;
while (c--) ans = ans * 2 % MOD;
return ans;
}
};
Go
func countWays(ranges [][]int) int {
const mod = 1e9 + 7
sort.Slice(ranges, func(i, j int) bool { return ranges[i][0] < ranges[j][0] })
c, end := 0, -1
for _, r := range ranges {
if r[0] > end {
c++
end = r[1]
} else {
if r[1] > end {
end = r[1]
}
}
}
ans := 1
for i := 0; i < c; i++ {
ans = ans * 2 % mod
}
return ans
}
Java
class Solution {
public int countWays(int[][] ranges) {
int MOD = 1_000_000_7;
Arrays.sort(ranges, (a, b) -> Integer.compare(a[0], b[0]));
int c = 0, end = -1;
for (int[] r : ranges) {
if (r[0] > end) {
c++;
end = r[1];
} else {
end = Math.max(end, r[1]);
}
}
long ans = 1;
for (int i = 0; i < c; i++) ans = ans * 2 % MOD;
return (int) ans;
}
}
Kotlin
class Solution {
fun countWays(ranges: Array<IntArray>): Int {
val MOD = 1_000_000_7
ranges.sortBy { it[0] }
var c = 0
var end = -1
for (r in ranges) {
if (r[0] > end) {
c++
end = r[1]
} else {
end = maxOf(end, r[1])
}
}
var ans = 1L
repeat(c) { ans = ans * 2 % MOD }
return ans.toInt()
}
}
Python
class Solution:
def countWays(self, ranges: list[list[int]]) -> int:
MOD = 10**9 + 7
ranges.sort()
c = 0
end = -1
for s, e in ranges:
if s > end:
c += 1
end = e
else:
end = max(end, e)
ans = 1
for _ in range(c):
ans = ans * 2 % MOD
return ans
Rust
impl Solution {
pub fn count_ways(ranges: Vec<Vec<i32>>) -> i32 {
let mut ranges = ranges;
ranges.sort();
let mut c = 0;
let mut end = -1;
for r in ranges.iter() {
if r[0] > end {
c += 1;
end = r[1];
} else {
end = end.max(r[1]);
}
}
let mut ans = 1i64;
for _ in 0..c {
ans = ans * 2 % 1_000_000_007;
}
ans as i32
}
}
TypeScript
class Solution {
countWays(ranges: number[][]): number {
const MOD = 1e9 + 7;
ranges.sort((a, b) => a[0] - b[0]);
let c = 0, end = -1;
for (const [s, e] of ranges) {
if (s > end) {
c++;
end = e;
} else {
end = Math.max(end, e);
}
}
let ans = 1;
for (let i = 0; i < c; i++) ans = ans * 2 % MOD;
return ans;
}
}
Complexity
- ⏰ Time complexity:
O(n log n), due to sorting the ranges. - 🧺 Space complexity:
O(1), ignoring the input and output, as only a few variables are used.