HomeLeetcode352. Data Stream as Disjoint Intervals - Leetcode Solutions

352. Data Stream as Disjoint Intervals – Leetcode Solutions

Description

Given a data stream input of non-negative integers a1, a2, ..., an, summarize the numbers seen so far as a list of disjoint intervals.

Implement the SummaryRanges class:

  • SummaryRanges() Initializes the object with an empty stream.
  • void addNum(int value) Adds the integer value to the stream.
  • int[][] getIntervals() Returns a summary of the integers in the stream currently as a list of disjoint intervals [starti, endi]. The answer should be sorted by starti.

Examples:

Example 1:

Input
["SummaryRanges", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals", "addNum", "getIntervals"]
[[], [1], [], [3], [], [7], [], [2], [], [6], []]
Output
[null, null, [[1, 1]], null, [[1, 1], [3, 3]], null, [[1, 1], [3, 3], [7, 7]], null, [[1, 3], [7, 7]], null, [[1, 3], [6, 7]]]

Solution in Python

To solve this problem efficiently, we need to manage a data stream of non-negative integers and maintain disjoint intervals in sorted order as new integers are added. The core idea is to use a balanced data structure like a sorted list to store intervals, and efficiently merge overlapping or adjacent intervals when a new number is added.

Plan:

  1. Initialization: We’ll maintain a sorted list of intervals. Each interval is represented as a pair [start, end].
  2. Add a number:
    • Insert the number into the correct position within the existing intervals.
    • Check if the number can extend an existing interval, either by merging with a previous or next interval, or by creating a new interval.
  3. Get Intervals: Simply return the current list of disjoint intervals.

Key Operations:

  • Merging intervals: When adding a new number, check if the number is adjacent or overlaps with any existing intervals, and merge them accordingly.
  • Sorted Insertion: We need to maintain the list of intervals in sorted order, so that when a new number is added, we can quickly find where to insert it.
Python
class SummaryRanges:
    def __init__(self):
        # Initialize an empty list to store the intervals
        self.intervals = []
    
    def addNum(self, value: int) -> None:
        # If no intervals exist yet, just add the first interval
        if not self.intervals:
            self.intervals.append([value, value])
            return
        
        # Find the correct position to insert the new value
        new_interval = [value, value]
        merged_intervals = []
        inserted = False
        
        for interval in self.intervals:
            if interval[1] < value - 1:  # Current interval is completely before the new value
                merged_intervals.append(interval)
            elif interval[0] > value + 1:  # Current interval is completely after the new value
                if not inserted:
                    merged_intervals.append(new_interval)
                    inserted = True
                merged_intervals.append(interval)
            else:  # Intervals overlap or are adjacent, we need to merge
                new_interval[0] = min(new_interval[0], interval[0])
                new_interval[1] = max(new_interval[1], interval[1])
        
        # If new_interval wasn't inserted, add it at the end
        if not inserted:
            merged_intervals.append(new_interval)
        
        # Update the intervals with the merged result
        self.intervals = merged_intervals

    def getIntervals(self) -> List[List[int]]:
        # Return the current list of disjoint intervals
        return self.intervals

Detailed Explanation:

  1. Initialization:
    • The constructor (__init__) initializes an empty list self.intervals to store the intervals.
  2. addNum Method:
    • For each new number, we create a new interval [value, value].
    • We iterate through the current intervals:
      • If the current interval ends before value - 1, it is completely before the new value, so we add it to the merged_intervals.
      • If the current interval starts after value + 1, it is completely after the new value. We insert the new interval before it if it hasn’t been inserted yet, then append the current interval.
      • Otherwise, the new number either overlaps or is adjacent to the current interval. We merge the new number into this interval by adjusting the start and end points of the new_interval.
    • If after iterating we haven’t inserted the new_interval, we append it at the end of the merged_intervals.
    • Finally, we update self.intervals with the merged intervals.
  3. getIntervals Method:
    • Simply returns the current list of intervals in sorted order.

Solution in C++

C++
class SummaryRanges {
private:
    // A set to store the unique numbers in the stream
    set<int> numbers;
    
public:
    // Constructor to initialize the object with an empty stream
    SummaryRanges() {
        // numbers set will hold the integers as they are added
    }

    // Method to add a number to the stream
    void addNum(int value) {
        // We use a set because it automatically handles duplicates and sorts the elements.
        numbers.insert(value);
    }

    // Method to return the summary of intervals
    vector<vector<int>> getIntervals() {
        vector<vector<int>> intervals;

        // If there are no numbers in the stream, return an empty vector
        if (numbers.empty()) return intervals;

        // Variables to track the start and end of the current interval
        int start = -1, end = -1;

        // Iterate through the sorted set of numbers
        for (int num : numbers) {
            if (start == -1) {
                // Initialize the start and end of the first interval
                start = end = num;
            } else if (num == end + 1) {
                // If the current number continues the interval (is consecutive)
                end = num;
            } else {
                // If the current number is not consecutive, store the previous interval
                intervals.push_back({start, end});
                // Start a new interval
                start = end = num;
            }
        }

        // Add the last interval
        intervals.push_back({start, end});

        return intervals;
    }
};

Solution in Javascript

JavaScript
// Constructor for the SummaryRanges class
var SummaryRanges = function() {
    // Initialize an empty array to store the intervals
    this.intervals = [];
};

/** 
 * @param {number} value
 * @return {void}
 */
SummaryRanges.prototype.addNum = function(value) {
    // Edge case: if no intervals exist, simply add the new number as a new interval
    if (this.intervals.length === 0) {
        this.intervals.push([value, value]);
        return;
    }

    // Find the appropriate place to insert the new number
    for (let i = 0; i < this.intervals.length; i++) {
        let [start, end] = this.intervals[i];
        
        // Case 1: The new value is already covered by an existing interval, so no need to add it
        if (value >= start && value <= end) {
            return;
        }

        // Case 2: The new value should extend an existing interval (either on the left or right)
        if (value === end + 1) {
            // Extend the current interval to the right
            this.intervals[i][1] = value;
            
            // Check if the next interval starts where the current interval ends
            if (i + 1 < this.intervals.length && this.intervals[i + 1][0] === value + 1) {
                // Merge the next interval with the current one
                this.intervals[i][1] = this.intervals[i + 1][1];
                this.intervals.splice(i + 1, 1); // Remove the next interval
            }
            return;
        }

        // Case 3: The new value should be inserted between two intervals
        if (value === start - 1) {
            // Extend the current interval to the left
            this.intervals[i][0] = value;
            return;
        }

        // Case 4: The new value lies before the current interval and does not overlap
        if (value < start) {
            // Insert a new interval for the value
            this.intervals.splice(i, 0, [value, value]);
            return;
        }
    }

    // Case 5: If the value is larger than all existing intervals, add it as a new interval at the end
    this.intervals.push([value, value]);
};

/**
 * @return {number[][]}
 */
SummaryRanges.prototype.getIntervals = function() {
    // Simply return the list of intervals
    return this.intervals;
};

Solution in Java

Java
public class SummaryRanges {
    // TreeMap to store the start of the interval as the key, and the end of the interval as the value.
    private TreeMap<Integer, Integer> intervals;

    // Constructor to initialize the object with an empty TreeMap.
    public SummaryRanges() {
        intervals = new TreeMap<>();
    }

    // Adds the integer value to the stream and updates the intervals accordingly.
    public void addNum(int value) {
        // Check if the value is already part of an existing interval
        if (intervals.containsKey(value)) {
            return; // The value is already in an interval, so no need to do anything.
        }

        // Find the intervals just before and after the new value
        Integer low = intervals.floorKey(value); // The largest key that is less than or equal to value
        Integer high = intervals.ceilingKey(value); // The smallest key that is greater than or equal to value

        // Case 1: Value is adjacent to an interval on both sides
        if (low != null && intervals.get(low) + 1 == value && high != null && high == value + 1) {
            // Merge the two adjacent intervals into one
            intervals.put(low, intervals.get(high));
            intervals.remove(high); // Remove the right interval since it's merged.
        }
        // Case 2: Value is adjacent only to the left interval
        else if (low != null && intervals.get(low) + 1 >= value) {
            // Extend the left interval to include the value
            intervals.put(low, Math.max(intervals.get(low), value));
        }
        // Case 3: Value is adjacent only to the right interval
        else if (high != null && high == value + 1) {
            // Insert the value as a new start of the interval
            intervals.put(value, intervals.get(high));
            intervals.remove(high); // Remove the right interval since it's merged.
        }
        // Case 4: Value is not adjacent to any interval
        else {
            // Create a new interval just for this value
            intervals.put(value, value);
        }
    }

    // Returns the current list of disjoint intervals.
    public int[][] getIntervals() {
        // Convert the TreeMap's entrySet to an array of intervals
        int[][] result = new int[intervals.size()][2];
        int i = 0;
        for (var entry : intervals.entrySet()) {
            result[i][0] = entry.getKey(); // Start of the interval
            result[i][1] = entry.getValue(); // End of the interval
            i++;
        }
        return result;
    }
}

Subscribe
Notify of

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Popular