Lesson 61 of 70 5 min

MANG Problem #33: Insert Interval (Hard)

Learn how to insert and merge overlapping intervals in a sorted list in O(n) time.

Reading Mode

Hide the curriculum rail and keep the lesson centered for focused reading.

1. Problem Statement

Mental Model

Breaking down a complex problem into its most efficient algorithmic primitive.

You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval.

Insert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]

2. Approach: Linear Scan & Merge

Since the list is already sorted, we can do this in exactly one pass ($O(n)$) without sorting again.

  1. Left Disjoint: All intervals ending before newInterval starts are added to the result.
  2. Overlap: All intervals that overlap with newInterval are merged by updating the boundaries of newInterval (start = min(starts), end = max(ends)).
  3. Right Disjoint: All intervals starting after the merged newInterval ends are added to the result.

3. Java Implementation

public int[][] insert(int[][] intervals, int[] newInterval) {
    List<int[]> result = new ArrayList<>();
    int i = 0, n = intervals.length;

    // 1. Add all intervals that end before newInterval starts
    while (i < n && intervals[i][1] < newInterval[0]) {
        result.add(intervals[i]);
        i++;
    }

    // 2. Merge overlapping intervals
    while (i < n && intervals[i][0] <= newInterval[1]) {
        newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
        newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
        i++;
    }
    result.add(newInterval); // Add the merged interval

    // 3. Add all remaining intervals
    while (i < n) {
        result.add(intervals[i]);
        i++;
    }

    return result.toArray(new int[result.size()][]);
}

4. 5-Minute "Video-Style" Walkthrough

  1. The "Aha!" Moment: Because the array is sorted, the intervals fall into three distinct buckets: strictly before, overlapping, and strictly after.
  2. The Overlap Condition: An interval overlaps if its start is $\le$ the new interval's end. (Since we already skipped those that end before it starts).
  3. The Merge: We don't add the overlapping intervals to the list. Instead, we expand our newInterval to "swallow" them. We only add newInterval to the result list once the overlap phase is completely over.

5. Interview Discussion

  • Interviewer: "What is the time complexity?"
  • You: "O(N), because we scan through the intervals exactly once."
  • Interviewer: "What if the original list was not sorted?"
  • You: "Then we would have to append the new interval and run the standard $O(N \log N)$ Merge Intervals algorithm."

5. Verbal Interview Script (Staff Tier)

Interviewer: "Walk me through your optimization strategy for this problem."

You: "When approaching this type of challenge, my primary objective is to identify the underlying Monotonicity or Optimal Substructure that allow us to bypass a naive brute-force search. In my implementation of 'MANG Problem #33: Insert Interval (Hard)', I focused on reducing the time complexity by leveraging a Dynamic Programming state transition. This allows us to handle input sizes that would typically cause a standard O(N^2) approach to fail. Furthermore, I prioritized memory efficiency by optimizing the DP state to use only a 1D array. This ensures that the application remains performant even under heavy garbage collection pressure in a high-concurrency Java environment."

6. Staff-Level Interview Follow-Ups

Once you provide the optimized solution, a senior interviewer at Google or Meta will likely push you further. Here is how to handle the most common follow-ups:

Follow-up 1: "How does this scale to a Distributed System?"

If the input data is too large to fit on a single machine (e.g., billions of records), we would move from a single-node algorithm to a MapReduce or Spark-based approach. We would shard the data based on a consistent hash of the keys and perform local aggregations before a global shuffle and merge phase, similar to the logic used in External Merge Sort.

Follow-up 2: "What are the Concurrency implications?"

In a multi-threaded Java environment, we must ensure that our state (e.g., the DP table or the frequency map) is thread-safe. While we could use synchronized blocks, a higher-performance approach would be to use AtomicVariables or ConcurrentHashMap. For problems involving shared arrays, I would consider a Work-Stealing pattern where each thread processes an independent segment of the data to minimize lock contention.

7. Performance Nuances (The Java Perspective)

  1. Autoboxing Overhead: When using HashMap<Integer, Integer>, Java performs autoboxing which creates thousands of Integer objects on the heap. In a performance-critical system, I would use a primitive-specialized library like fastutil or Trove to use Int2IntMap, significantly reducing GC pauses.
  2. Recursion Depth: As discussed in the code, recursive solutions are elegant but risky for deep inputs. I always ensure the recursion depth is bounded, or I rewrite the logic to be Iterative using an explicit stack on the heap to avoid StackOverflowError.

Key Takeaways

  • Interviewer: "What is the time complexity?"
  • You: "O(N), because we scan through the intervals exactly once."
  • Interviewer: "What if the original list was not sorted?"

Want to track your progress?

Sign in to save your progress, track completed lessons, and pick up where you left off.