A Range Module is a module that tracks ranges of numbers. Design a data structure to track the ranges represented as half-open intervals and query about them.
A half-open interval [left, right)
denotes all the real numbers x
where left <= x < right
.
Implement the RangeModule
class:
RangeModule()
Initializes the object of the data structure.void addRange(int left, int right)
Adds the half-open interval [left, right)
, tracking every real number in that interval. Adding an interval that partially overlaps with currently tracked numbers should add any numbers in the interval [left, right)
that are not already tracked.boolean queryRange(int left, int right)
Returns true
if every real number in the interval [left, right)
is currently being tracked, and false
otherwise.void removeRange(int left, int right)
Stops tracking every real number currently being tracked in the half-open interval [left, right)
.Example:
Input
["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "queryRange"]
[[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]]
Output
[null, null, null, true, false, true]
Explanation
RangeModule rangeModule = new RangeModule();
rangeModule.addRange(10, 20);
rangeModule.removeRange(14, 16);
rangeModule.queryRange(10, 14); // return True,(Every number in [10, 14) is being tracked)
rangeModule.queryRange(13, 15); // return False,(Numbers like 14, 14.03, 14.17 in [13, 15) are not being tracked)
rangeModule.queryRange(16, 17); // return True, (The number 16 in [16, 17) is still being tracked, despite the remove operation)
Constraints:
1 <= left < right <= 10^9
10^4
calls will be made to addRange
, queryRange
, and removeRange
.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The Range Module problem involves keeping track of numerical intervals. A brute force approach directly manipulates the intervals by checking every number within the given ranges for insertions, deletions, or queries.
Here's how the algorithm would work step-by-step:
class RangeModule:
def __init__(self):
self.tracked_numbers = set()
def addRange(self, left_bound: int, right_bound: int) -> None:
for number in range(left_bound, right_bound):
# We add the number if it's not tracked yet
if number not in self.tracked_numbers:
self.tracked_numbers.add(number)
def removeRange(self, left_bound: int, right_bound: int) -> None:
for number in range(left_bound, right_bound):
# Remove tracked numbers within the range
if number in self.tracked_numbers:
self.tracked_numbers.remove(number)
def queryRange(self, left_bound: int, right_bound: int) -> bool:
for number in range(left_bound, right_bound):
# If any number is missing, the range isn't tracked
if number not in self.tracked_numbers:
return False
return True
The problem deals with keeping track of intervals of numbers. The best way is to maintain a collection of sorted intervals and efficiently merge or split them when new ranges are added or removed. This avoids checking every single number within the range.
Here's how the algorithm would work step-by-step:
class RangeModule:
def __init__(self):
self.intervals = []
def addRange(self, left, right):
new_interval = [left, right]
new_intervals = []
insert_position = 0
for interval_start, interval_end in self.intervals:
# No overlap, new interval is completely before
if new_interval[1] < interval_start:
new_intervals.append([interval_start, interval_end])
# No overlap, new interval is completely after
elif new_interval[0] > interval_end:
new_intervals.append([interval_start, interval_end])
insert_position += 1
# Overlap, merge the intervals
else:
new_interval[0] = min(new_interval[0], interval_start)
new_interval[1] = max(new_interval[1], interval_end)
# Insert the merged/new interval in the correct sorted position
new_intervals.insert(insert_position, new_interval)
self.intervals = new_intervals
def queryRange(self, left, right):
for interval_start, interval_end in self.intervals:
# If any interval contains the range, return True
if interval_start <= left and interval_end >= right:
return True
return False
def removeRange(self, left, right):
new_intervals = []
for interval_start, interval_end in self.intervals:
# No overlap, keep the existing interval
if right < interval_start or left > interval_end:
new_intervals.append([interval_start, interval_end])
# Interval is completely contained in the range to remove
elif left <= interval_start and right >= interval_end:
continue
# Partial overlap, split the interval into two
else:
if left > interval_start:
new_intervals.append([interval_start, left])
if right < interval_end:
new_intervals.append([right, interval_end])
# Updating the intervals is necessary after removal
self.intervals = new_intervals
Case | How to Handle |
---|---|
Empty range module (no intervals added yet) | Ensure query and remove functions handle empty module gracefully, returning appropriate boolean values or doing nothing without errors. |
Adding a range that completely overlaps an existing range | The new range should completely replace the existing range, effectively merging them. |
Adding a range that partially overlaps an existing range (left or right) | The existing range should be expanded to include the new range, creating a single merged range. |
Adding a range that is adjacent to an existing range | The adjacent ranges should be merged into a single range. |
Removing a range that completely contains existing ranges | Split the containing range into at most two ranges, one before the removed range and one after, if necessary. |
Removing a range that partially overlaps existing ranges (left or right) | The partially overlapped ranges should be truncated to exclude the removed range. |
Querying for a range that falls exactly on the boundary of existing ranges | The query function should correctly determine if the range is contained within or intersects with existing ranges considering closed interval boundaries. |
Integer overflow when calculating the sum or difference of range endpoints | Use appropriate data types (e.g., long) to prevent potential integer overflows when performing arithmetic operations on range endpoints. |