Skip to content
ΣDSA Patterns
Menu
Language

Sweep Line

Guide 3 of 6 · Path 3 of 6

Interactive

Mental model

A worked animation for this problem. Scrub steps or press space to pause; re-tell the invariant out loud.

Step 1 of 6

getIntervals = []

Stream of integers → disjoint closed intervals. Start empty.

Demo preview: These solutions pass the automated test suite but have not been human-reviewed. Treat trade-offs and prose as draft.

Data Stream as Disjoint Intervals

Problem (restated)

Stream of integers. After each addNum(val), the structure should expose getIntervals(): the disjoint closed intervals covering every added value, sorted, merged whenever two intervals touch or overlap.

Intuition

Keep the current disjoint list sorted. A new point is a degenerate interval [val, val]. Sweep the existing intervals: those strictly left stay, those that overlap or touch (b >= val-1 and a <= val+1) merge into it, those strictly right stay after it.

Approaches

Insert and merge

Unverified
Time O(n) add / O(n) getSpace O(n)

Idea. One pass over the sorted list. b < val-1 → copy. a > val+1 → emit the new interval once, then copy. Else expand [val,val] to cover [a,b]. Append at the end if it was the rightmost.

Walkthrough. 1, 3, 7 → [[1,1],[3,3],[7,7]]. Add 2 → [1,3] merges. Add 6 → [6,7].

Trade-offs. Linear scan is fine while the unique-count is small. A balanced tree of intervals is O(log n) insert; interviews usually accept the scan. Touching counts as merge ([1,1] and [2,2] become [1,2]).

Solution
export class SummaryRanges {
  private ivs: number[][] = [];

  addNum(val: number): void {
    const n = [val, val];
    const res: number[][] = [];
    let placed = false;
    for (const iv of this.ivs) {
      const a = iv[0]!, b = iv[1]!;
      if (b < n[0]! - 1) res.push([a, b]);
      else if (a > n[1]! + 1) {
        if (!placed) { res.push(n); placed = true; }
        res.push([a, b]);
      } else {
        n[0] = Math.min(n[0]!, a);
        n[1] = Math.max(n[1]!, b);
      }
    }
    if (!placed) res.push(n);
    this.ivs = res;
  }

  getIntervals(): number[][] {
    return this.ivs;
  }
}
export class SummaryRanges {
  private ivs: number[][] = [];

  addNum(val: number): void {
    const n = [val, val];
    const res: number[][] = [];
    let placed = false;
    for (const iv of this.ivs) {
      const a = iv[0]!, b = iv[1]!;
      if (b < n[0]! - 1) res.push([a, b]);
      else if (a > n[1]! + 1) {
        if (!placed) { res.push(n); placed = true; }
        res.push([a, b]);
      } else {
        n[0] = Math.min(n[0]!, a);
        n[1] = Math.max(n[1]!, b);
      }
    }
    if (!placed) res.push(n);
    this.ivs = res;
  }

  getIntervals(): number[][] {
    return this.ivs;
  }
}

Template connection

Data-stream shape of Sweep Line: each insert is an event that is merged into the active disjoint set. Same merge rule as interval merge (LC 56), online.

Reflection