Coding Drills ยท 04

Binary Search & Intervals

Two ideas that both start with "sort it". Binary search throws away half of a sorted (or merely monotone) search space per probe, turning a million candidates into twenty comparisons. Interval problems sort by start time so that every overlap becomes a neighbour, and then a single sweep resolves them. Drills 33โ€“39 cover both, from the five-line lower bound to the partition trick behind the median of two sorted arrays.

7
Drills
2
Easy
4
Medium
1
Hard
01
Chapter One ยท Core Idea

Order Lets You Skip Work

Binary search is not really about sorted arrays. It is about a yes/no question whose answers, read left to right, look like no, no, no, yes, yes, yes. "Is nums[i] >= target?" on a sorted array has that shape. So does "is nums[i] > nums[i + 1]?" when looking for a peak, and "is nums[i] <= nums[last]?" on a rotated array. Once the question is monotone, probing the middle tells you which half cannot contain the boundary, and you discard it: n โ†’ n/2 โ†’ n/4 โ†’ โ€ฆ โ†’ 1 in โŒˆlogโ‚‚ nโŒ‰ steps.

Interval problems use order differently. A set of ranges [start, end] can overlap in tangled ways, but once they are sorted by start, an interval can only overlap the ones immediately before it in that order. Sorting costs O(n log n); the sweep that follows is O(n).

Two uses of order โ€” a monotone predicate, and sorted starts
BINARY SEARCH โ€” predicate nums[i] โ‰ฅ 7 1 3 5 6 8 9 12 15 no no no no yes yes yes yes boundary = index 4 each probe at mid discards half the cells 10โถ cells โ†’ 20 probes INTERVALS โ€” sorted by start, overlaps become neighbours [1,3] [2,6] [8,10] [15,18] merged 0 5 10 15 20
Analogy: Guessing a number between 1 and 1,000,000 when the only feedback is "higher" or "lower". Guessing in order takes up to a million tries; always guessing the middle of what is left takes at most 20. Intervals are a calendar: shuffled meeting cards are hard to reason about, but laid out in start-time order you only ever compare a card with the one before it.
๐ŸŽฏ

Lower bound

First index where the predicate turns true. Drill 33.

โ›ฐ๏ธ

Neighbour compare

No global sort needed โ€” only a local slope or a comparison with the end. Drills 34, 35.

๐Ÿงญ

Search in 2D

Staircase walk, or flatten to one index. Drill 36.

โœ‚๏ธ

Search a partition

Binary search over how many items go left. Drill 37.

๐Ÿงท

Sort + sweep

Merge touching ranges in one pass. Drill 38.

๐Ÿ“…

Overlap / rooms

Adjacent check, or a min-heap of end times. Drill 39.

02
Chapter Two ยท Mental Model

How These Problems Think

A predicate is false for a prefix of indices and true for the rest
โ–ถ
One probe at mid tells you which side of the boundary mid is on โ€” the other half can be discarded without looking at it
The search space halves every iteration
โ–ถ
โŒˆlogโ‚‚ nโŒ‰ iterations: 2ยณยน elements need at most 31 probes, so binary search is never the bottleneck
Every bug lives in three choices: loop condition, how mid rounds, and how bounds move
โ–ถ
Pick one template, state its invariant ("the answer is always in [lo, hi)"), and derive the three choices from it instead of guessing
You can binary-search anything with a monotone yes/no answer โ€” not just array cells
โ–ถ
Drill 37 searches over "how many elements of A go in the left half", a number from 0 to m
Intervals sorted by start
โ–ถ
If interval k does not overlap the merged block before it, no later interval can either โ€” so one running "current end" replaces an O(nยฒ) pairwise check
You need "how many overlap at once", not just "do any overlap"
โ–ถ
Keep the end times of the active intervals in a min-heap: the earliest end tells you whether a room frees up
ranges [start, end] that may overlap? yes Sort by start + sweep Drills 38 ยท 39 no median / k-th of two sorted arrays? yes Search the partition โ€” 37 no 2D grid, rows and columns sorted? yes Staircase / flatten โ€” 36 no fully sorted 1D, find value / slot? yes Lower bound โ€” Drill 33 no only a local rule (slope, vs. last)? โ†’ neighbour compare (34, 35)
Signals in the problem statement:
  • "Must run in O(log n)" is the interviewer telling you to binary search โ€” even if the array is not sorted in the usual sense (Drills 34, 35).
  • "Sorted" plus "find" or "insert" โ†’ lower bound. Memorise it once; use it everywhere.
  • "Intervals", "meetings", "bookings", "ranges" โ†’ sort by start (or sweep start/end events) before anything else.
03
Chapter Three ยท Pattern Toolkit

Three Templates That Never Off-By-One

There are many correct ways to write binary search and far more incorrect ones. The cure is to commit to a small number of templates, each with a stated invariant, and never mix their pieces.

Template 1 โ€” Lower Bound on a Half-Open Range
Find the first index whose value is โ‰ฅ target (or n if none). Invariant: the answer is always in [lo, hi], and every index left of lo is too small.
Pseudocode
function lowerBound(a, target): lo = 0, hi = n // hi = n: "insert at the end" is legal while lo < hi: // stop when the range is empty mid = lo + (hi - lo) / 2 // rounds down, so mid < hi if a[mid] < target: lo = mid + 1 // mid is too small: discard it else: hi = mid // mid might be the answer: keep it return lo // lo == hi
Lower bound of 2 in [1, 3, 5, 6] โ€” the range [lo, hi) shrinks to empty
0 1 2 3 4 = n lo=0 hi=4 1 3 5 6 mid=2: 5 โ‰ฅ 2 โ†’ hi = 2 lo=0 hi=2 1 3 5 6 mid=1: 3 โ‰ฅ 2 โ†’ hi = 1 lo=0 hi=1 1 3 5 6 mid=0: 1 < 2 โ†’ lo = 1 lo=1 hi=1 1 3 5 6 empty range โ†’ answer 1 gold = mid ยท blue = still inside [lo, hi) ยท grey = discarded
Template 2 โ€” Converge on One Index (lo < hi, hi = mid)
For problems where the answer always exists (a peak, a minimum): keep both ends inclusive, loop while more than one candidate remains, and return lo when they meet.
Pseudocode
lo = 0, hi = n - 1 while lo < hi: mid = lo + (hi - lo) / 2 // lower middle: lo โ‰ค mid < hi if answerIsRightOf(mid): lo = mid + 1 else: hi = mid // mid stays a candidate return lo
Why mid must round DOWN when the update is hi = mid / lo = mid + 1
mid = lo + (hi โˆ’ lo) / 2 lo=mid hi go right: lo = mid + 1 = hi โ†’ size 1 โœ“ go left: hi = mid = lo โ†’ size 1 โœ“ both branches shrink โ†’ terminates mid = lo + (hi โˆ’ lo + 1) / 2 lo hi=mid go right: lo = mid + 1 > hi โ†’ skips hi โœ— go left: hi = mid = hi โ†’ no change โœ— infinite loop on a 2-element range rule: if the update is lo = mid, round up; if it is hi = mid, round down
TemplateLoopBoundsUse for
Exact matchlo <= hilo = mid + 1 / hi = mid - 1"Is x present?" โ€” returns inside the loop
Lower bound (T1)lo < hi, hi = nlo = mid + 1 / hi = midInsert position, first โ‰ฅ, count โ‰ค
Converge (T2)lo < hi, hi = n โˆ’ 1lo = mid + 1 / hi = midPeak, rotated minimum โ€” answer always exists
Template 3 โ€” Sort by Start, Then Sweep
Sort intervals by start. Keep one "current" merged block. Each next interval either extends it (start โ‰ค current end) or closes it and starts a new one.
Pseudocode
sort(intervals, by start) current = intervals[0] for each next in intervals[1..]: if next.start <= current.end: // overlap (or touch) current.end = max(current.end, next.end) // max! next may be nested else: emit current; current = next emit current // O(n log n) sort + O(n) sweep
Sweep over [1,4], [2,3], [5,7], [6,8] โ€” a nested interval must not shrink the block
[1,4] current = [1,4] [2,3] 2 โ‰ค 4 โ†’ max(4,3)=4 [5,7] 5 > 4 โ†’ emit, new [6,8] 6 โ‰ค 7 โ†’ end 8 end stays 4 result [1,4] [5,8] 0 1 2 3 4 5 6 7 8
04
Drill 33 ยท Lower Bound

Search Insert Position

๐Ÿท๏ธ

Difficulty

Easy

๐Ÿ”—

LeetCode

LC 35 ยท full write-up

๐Ÿงฉ

Pattern

binary search lower bound

Given a sorted array of distinct integers and a target, return the index of the target if present; otherwise return the index where it would be inserted to keep the array sorted. Must run in O(log n).

Example
Input: nums = [1, 3, 5, 6], target = 5 Output: 2 Input: nums = [1, 3, 5, 6], target = 2 Output: 1 Input: nums = [1, 3, 5, 6], target = 7 Output: 4
Key insight: "Where it is, or where it would go" is one question: the first index whose value is โ‰ฅ target. That is the lower bound from Template 1, word for word. You do not need a separate "found" branch at all.

Brute force โ†’ optimal. Scanning left to right until nums[i] >= target is O(n). Because the predicate nums[i] >= target is false-then-true on a sorted array, binary search finds the switch point in O(log n). Using the half-open range [0, n) makes "insert at the end" (answer n) fall out naturally.

Every possible answer for [1, 3, 5, 6] โ€” n + 1 slots, not n
0 1 2 3 1 3 5 6 target 0 โ†’ 0 target 2 โ†’ 1 target 5 โ†’ 2 (found) target 7 โ†’ 4 = n the answer is always "how many elements are < target"
Walkthrough โ€” target = 7 (insert at the end)
steplohimidnums[mid]decision
104255 < 7 โ†’ lo = 3
234366 < 7 โ†’ lo = 4
โ€”44โ€”โ€”lo == hi โ†’ return 4
Java โ€” half-open lower bound
public class SearchInsert { // Lower bound: first index whose value is >= target public static int searchInsert(int[] nums, int target) { int lo = 0, hi = nums.length; // half-open range [lo, hi) while (lo < hi) { int mid = lo + (hi - lo) / 2; // no int overflow if (nums[mid] < target) lo = mid + 1; // answer is to the right of mid else hi = mid; // mid could be the answer } return lo; } public static void main(String[] args) { int[] nums = {1, 3, 5, 6}; System.out.println(searchInsert(nums, 5)); // 2 (found) System.out.println(searchInsert(nums, 2)); // 1 (insert between 1 and 3) System.out.println(searchInsert(nums, 7)); // 4 (append at the end) System.out.println(searchInsert(nums, 0)); // 0 (insert at the front) } }
ApproachTimeSpaceNote
Linear scanO(n)O(1)Ignores sortedness
Lower boundO(log n)O(1)Same as Arrays.binarySearch's insertion point
Common bugs:
  • int mid = (lo + hi) / 2 overflows when lo + hi > Integer.MAX_VALUE. Use lo + (hi - lo) / 2 (or (lo + hi) >>> 1).
  • Starting with hi = n - 1 in the half-open template โ€” target 7 would return 3 instead of 4.
  • Arrays.binarySearch returns -(insertionPoint) - 1 when not found, not the insertion point itself.
  • With duplicates, this template returns the first equal index โ€” the exact-match template may return any of them.
05
Drill 34 ยท Neighbour Compare

Find Peak Element

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

LC 162 ยท full write-up

๐Ÿงฉ

Pattern

binary search on slope

A peak is an element strictly greater than its neighbours; positions outside the array count as โˆ’โˆž. Adjacent elements are never equal. Return the index of any peak in O(log n).

Example
Input: nums = [1, 2, 3, 1] Output: 2 Input: nums = [1, 2, 1, 3, 5, 6, 4] Output: 5 // index 1 is also valid
Key insight: You are hiking in fog and can only see one step ahead. If the ground rises to your right, there must be a summit somewhere to your right โ€” the path cannot rise forever, because past the end is โˆ’โˆž. If it falls to your right, there is a summit at your feet or to your left. Either way, one comparison nums[mid] vs nums[mid + 1] rules out half the array.

Brute force โ†’ optimal. A linear scan for the first i with nums[i] > nums[i + 1] is O(n). The array is not sorted, but the predicate "slope goes down at i" has a guaranteed true somewhere in every uphill-ending range, which is all Template 2 needs. The array need not be monotone overall โ€” we are not finding the maximum, only a peak.

Climbing [1, 2, 1, 3, 5, 6, 4] โ€” three probes
1 2 1 3 5 6 peak 4 0 1 2 3 4 5 6 probe 1 probe 2 probe 3 1: lo=0 hi=6 mid=3 3 < 5 uphill โ†’ lo=4 2: lo=4 hi=6 mid=5 6 > 4 downhill โ†’ hi=5 3: lo=4 hi=5 mid=4 5 < 6 uphill โ†’ lo=5 lo == hi == 5 also a peak
Java โ€” Template 2 on the slope
public class FindPeakElement { public static int findPeakElement(int[] nums) { int lo = 0, hi = nums.length - 1; while (lo < hi) { int mid = lo + (hi - lo) / 2; // mid + 1 always exists here if (nums[mid] > nums[mid + 1]) hi = mid; // downhill: a peak is at mid or left else lo = mid + 1; // uphill: a peak is strictly right } return lo; // lo == hi is a peak } public static void main(String[] args) { System.out.println(findPeakElement(new int[]{1, 2, 3, 1})); // 2 System.out.println(findPeakElement(new int[]{1, 2, 1, 3, 5, 6, 4})); // 5 } }
ApproachTimeSpaceNote
Linear scan for first descentO(n)O(1)Correct, too slow for the stated bound
Binary search on slopeO(log n)O(1)Needs adjacent elements to differ
Common bugs:
  • Using lo <= hi โ€” when lo == hi == n - 1, nums[mid + 1] is out of bounds. With lo < hi and a rounded-down mid, mid + 1 <= hi always.
  • Comparing with nums[mid - 1] as well and branching three ways โ€” more code, more bounds checks, no benefit.
  • Assuming the result is the global maximum. It is a peak; [1, 2, 1, 3, 5, 6, 4] has two.
  • If equal neighbours were allowed, the argument breaks: [1, 1, 1, 2, 1, 1] cannot be searched in O(log n) in the worst case.
06
Drill 35 ยท Neighbour Compare

Find Minimum in Rotated Sorted Array

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

LC 153 ยท full write-up

๐Ÿงฉ

Pattern

binary search compare with hi

A sorted array of distinct values was rotated at an unknown pivot, e.g. [0,1,2,4,5,6,7] became [4,5,6,7,0,1,2]. Return the minimum element in O(log n).

Example
Input: nums = [4, 5, 6, 7, 0, 1, 2] Output: 0 Input: nums = [3, 4, 5, 1, 2] Output: 1 Input: nums = [11, 13, 15, 17] Output: 11 // rotated 0 times
Key insight: A rotated array is two ascending ramps, and every element of the left ramp is larger than every element of the right ramp. Compare nums[mid] with nums[hi]: if it is bigger, mid is on the left ramp and the drop (the minimum) is strictly to its right; otherwise mid is on the right ramp and the minimum is at mid or to its left. The predicate "nums[i] <= nums[last]" is false-then-true, and the minimum is its first true.

Brute force โ†’ optimal. A linear scan for the drop (or simply the min) is O(n). Comparing mid with hi โ€” not with lo โ€” gives a clean two-way split with no special case for the un-rotated array, because in a sorted array nums[mid] <= nums[hi] always and hi marches left to index 0.

Walkthrough โ€” [4, 5, 6, 7, 0, 1, 2]
steplohimidnums[mid] vs nums[hi]decision
10637 > 2left ramp โ†’ lo = 4
24651 โ‰ค 2right ramp โ†’ hi = 5
34540 โ‰ค 1right ramp โ†’ hi = 4
โ€”44โ€”โ€”return nums[4] = 0
Java โ€” Template 2, compare with nums[hi]
public class FindMinRotated { public static int findMin(int[] nums) { int lo = 0, hi = nums.length - 1; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (nums[mid] > nums[hi]) lo = mid + 1; // drop is to the right of mid else hi = mid; // mid..hi is sorted: min is at mid or left } return nums[lo]; } public static void main(String[] args) { System.out.println(findMin(new int[]{4, 5, 6, 7, 0, 1, 2})); // 0 System.out.println(findMin(new int[]{3, 4, 5, 1, 2})); // 1 System.out.println(findMin(new int[]{11, 13, 15, 17})); // 11 (not rotated) } }
ApproachTimeSpaceNote
Linear minO(n)O(1)Ignores structure
Binary search vs nums[hi]O(log n)O(1)Distinct values required
Same with duplicates (LC 154)O(log n) avg, O(n) worstO(1)On a tie, hi--
Common bugs:
  • Comparing with nums[lo] instead of nums[hi]: for an un-rotated array nums[mid] > nums[lo] would push you right, away from the minimum.
  • Writing hi = mid - 1 in the "right ramp" branch โ€” mid itself may be the minimum (step 3 above).
  • With duplicates ([3,3,1,3]), nums[mid] == nums[hi] tells you nothing; this code is only correct for distinct values.
07
Drill 36 ยท Search in 2D

Search a 2D Sorted Matrix

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

LC 240 ยท variant LC 74

๐Ÿงฉ

Pattern

staircase search flattened index

An m ร— n matrix has every row sorted left to right and every column sorted top to bottom. Decide whether target is in it. (Variant LC 74 is stricter: each row also starts after the previous row ends, so the whole matrix is one sorted list.)

Example
Input: matrix = [[ 1, 4, 7, 11, 15], [ 2, 5, 8, 12, 19], [ 3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30]] target = 5 โ†’ true target = 20 โ†’ false
Key insight: Stand at the top-right corner. Everything to your left in this row is smaller; everything below you in this column is larger. So the corner is a decision point like the middle of a binary search: if it is too big, the whole column is too big (step left); if it is too small, the whole row is too small (step down). Each step deletes a row or a column.

Brute force โ†’ optimal. Scanning every cell is O(mยทn). Binary searching each row is O(m log n). The staircase walk is O(m + n), which is optimal for this matrix shape. For the LC 74 shape, treat index k in [0, mยทn) as cell (k / n, k % n) and run one ordinary binary search: O(log(mยทn)). The top-left corner does not work as a start โ€” both neighbours are larger, so there is no decision.

Staircase from the top-right corner, target = 5
1 4 4 7 3 11 2 15 1 2 5 5 8 12 19 3 6 9 16 22 10 13 14 17 24 18 21 23 26 30 1 [0][4] = 15 > 5 โ†’ col-- (drop column 4) 2 [0][3] = 11 > 5 โ†’ col-- (drop column 3) 3 [0][2] = 7 > 5 โ†’ col-- (drop column 2) 4 [0][1] = 4 < 5 โ†’ row++ (drop row 0) 5 [1][1] = 5 โ†’ found grey cells were eliminated without ever being read each step removes a whole row or column โ†’ at most m + n โˆ’ 1 steps
Java โ€” staircase (LC 240) and flattened binary search (LC 74)
public class SearchMatrix { // Rows sorted left to right, columns sorted top to bottom (LC 240) public static boolean searchMatrix(int[][] matrix, int target) { if (matrix.length == 0 || matrix[0].length == 0) return false; int row = 0, col = matrix[0].length - 1; // start at the top-right corner while (row < matrix.length && col >= 0) { int v = matrix[row][col]; if (v == target) return true; if (v > target) col--; // whole column below is even bigger else row++; // whole row to the left is even smaller } return false; } // Variant LC 74: each row starts after the previous row ends -> one flat binary search public static boolean searchFlattened(int[][] matrix, int target) { int rows = matrix.length, cols = matrix[0].length; int lo = 0, hi = rows * cols - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; int v = matrix[mid / cols][mid % cols]; // index -> (row, col) if (v == target) return true; if (v < target) lo = mid + 1; else hi = mid - 1; } return false; } public static void main(String[] args) { int[][] matrix = { {1, 4, 7, 11, 15}, {2, 5, 8, 12, 19}, {3, 6, 9, 16, 22}, {10, 13, 14, 17, 24}, {18, 21, 23, 26, 30} }; System.out.println(searchMatrix(matrix, 5)); // true System.out.println(searchMatrix(matrix, 20)); // false int[][] flat = {{1, 3, 5, 7}, {10, 11, 16, 20}, {23, 30, 34, 60}}; System.out.println(searchFlattened(flat, 3)); // true } }
ApproachTimeSpaceNote
Scan every cellO(m ยท n)O(1)Ignores both orderings
Binary search each rowO(m log n)O(1)Uses only the row ordering
Staircase from top-rightO(m + n)O(1)Rows and columns sorted (LC 240)
Flattened binary searchO(log(m ยท n))O(1)Only when rows chain (LC 74)
Common bugs:
  • Starting at the top-left (or bottom-right): both moves increase (or decrease) the value, so there is no rule for which way to go.
  • Using the flattened search on an LC 240 matrix โ€” row 1 (2, 5, 8 โ€ฆ) is not entirely greater than row 0, so the flat order is not sorted.
  • Index math with columns: row = k / cols, col = k % cols โ€” dividing by rows is wrong for non-square matrices.
  • Not guarding matrix.length == 0 || matrix[0].length == 0 before reading matrix[0].length.
08
Drill 37 ยท Search a Partition

Median of Two Sorted Arrays

๐Ÿท๏ธ

Difficulty

Hard

๐Ÿ”—

LeetCode

LC 4 ยท full write-up

๐Ÿงฉ

Pattern

binary search on partition

Given sorted arrays a (length m) and b (length n), return the median of all m + n values. The required complexity is O(log(m + n)) โ€” merging is not allowed.

Example
Input: a = [1, 3], b = [2] Output: 2.0 Input: a = [1, 2], b = [3, 4] Output: 2.5 Input: a = [1, 3, 8, 9, 15], b = [7, 11, 18, 19, 21, 25] Output: 11.0
Key insight: The median splits the merged array into a left half and a right half of equal size (left gets the extra one when the total is odd). Choose how many elements i come from a; then j = half โˆ’ i must come from b. The split is correct exactly when a[iโˆ’1] โ‰ค b[j] and b[jโˆ’1] โ‰ค a[i] โ€” every left element โ‰ค every right element. If a[iโˆ’1] is too big, take fewer from a; if b[jโˆ’1] is too big, take more. That is a monotone yes/no over i, so binary search it.

Brute force โ†’ optimal. Merging both arrays and picking the middle is O(m + n) time and space; a two-pointer walk to the middle drops the space to O(1) but is still O(m + n). Binary searching i over the shorter array gives O(log min(m, n)). Searching the shorter array also guarantees j stays within [0, n]. Sentinels (Integer.MIN_VALUE / MAX_VALUE) stand in for "nothing on this side" when i or j is 0 or at the end.

a = [1, 3, 8, 9, 15], b = [7, 11, 18, 19, 21, 25] โ€” total 11, half = 6
try 1 ยท i = 2, j = 4 a 1 3 8 9 15 b 7 11 18 19 21 25 aLeft=3 aRight=8 bLeft=19 bRight=21 bLeft 19 > aRight 8 โœ— too few from a โ†’ lo = 3 try 2 ยท i = 4, j = 2 a 1 3 8 9 15 b 7 11 18 19 21 25 aLeft=9 aRight=15 bLeft=11 bRight=18 9 โ‰ค 18 and 11 โ‰ค 15 โœ“ odd total โ†’ max(9, 11) = 11 blue = left half (6 values) ยท i searched in [0, 5] on the shorter array: try i=2, then i=4
Java โ€” binary search the cut in the shorter array
public class MedianOfTwoSortedArrays { public static double findMedianSortedArrays(int[] a, int[] b) { if (a.length > b.length) return findMedianSortedArrays(b, a); // search the shorter int m = a.length, n = b.length; int half = (m + n + 1) / 2; // size of the combined left half int lo = 0, hi = m; while (lo <= hi) { int i = lo + (hi - lo) / 2; // elements taken from a int j = half - i; // elements taken from b int aLeft = i == 0 ? Integer.MIN_VALUE : a[i - 1]; int aRight = i == m ? Integer.MAX_VALUE : a[i]; int bLeft = j == 0 ? Integer.MIN_VALUE : b[j - 1]; int bRight = j == n ? Integer.MAX_VALUE : b[j]; if (aLeft <= bRight && bLeft <= aRight) { // correct partition if ((m + n) % 2 == 1) return Math.max(aLeft, bLeft); return ((long) Math.max(aLeft, bLeft) + Math.min(aRight, bRight)) / 2.0; } else if (aLeft > bRight) { hi = i - 1; // took too many from a } else { lo = i + 1; // took too few from a } } throw new IllegalArgumentException("input arrays must be sorted"); } public static void main(String[] args) { System.out.println(findMedianSortedArrays(new int[]{1, 3}, new int[]{2})); // 2.0 System.out.println(findMedianSortedArrays(new int[]{1, 2}, new int[]{3, 4})); // 2.5 System.out.println(findMedianSortedArrays( new int[]{1, 3, 8, 9, 15}, new int[]{7, 11, 18, 19, 21, 25})); // 11.0 } }
ApproachTimeSpaceNote
Merge, take middleO(m + n)O(m + n)Simplest, violates the bound
Two-pointer walk to the middleO(m + n)O(1)Merge without storing
Binary search on partitionO(log min(m, n))O(1)Swap so a is shorter
Common bugs (two were in the original version of this drill):
  • Not searching the shorter array: with a longer, j = half โˆ’ i can go negative and index out of bounds.
  • Averaging with (x + y) / 2.0 on ints: if both are near Integer.MAX_VALUE the sum overflows before the division. Widen to long first.
  • Falling out of the loop with return 0.0 hides bad input; throwing makes an unsorted input fail loudly.
  • half = (m + n) / 2 instead of (m + n + 1) / 2 puts the extra element on the right, and then the odd-case answer is min(aRight, bRight), not max(aLeft, bLeft). Pick one convention and keep it.
09
Drill 38 ยท Sort + Sweep

Merge Intervals

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

LC 56 ยท full write-up

๐Ÿงฉ

Pattern

sort by start sweep

Given an array of intervals [start, end] in any order, merge all overlapping intervals and return the non-overlapping intervals that cover exactly the same points. Intervals that touch ([1,4] and [4,5]) count as overlapping.

Example
Input: [[1,3], [8,10], [2,6], [15,18]] Output: [[1,6], [8,10], [15,18]] Input: [[1,4], [4,5]] Output: [[1,5]]
Key insight: After sorting by start, the next interval either begins inside the current merged block (start โ‰ค current end โ†’ extend) or after it (start > current end โ†’ the block is final, because every later interval starts even later). One pass, one "current" block.

Brute force โ†’ optimal. Repeatedly scanning all pairs and merging any two that overlap until nothing changes is O(nยฒ) per pass and up to O(nยณ) overall. Sorting first costs O(n log n) and makes the merge a single O(n) sweep โ€” Template 3.

After sorting: [1,3] [2,6] [8,10] [15,18]
[1,3] [2,6] 2 โ‰ค 3 โ†’ extend to 6 [8,10] 8 > 6 โ†’ new block [15,18] 15 > 10 โ†’ new merged [1,6] [8,10] [15,18] 0 5 10 15 20
Walkthrough โ€” the sweep
nextcurrent beforetestactionoutput so far
[1,3][1,3]1 โ‰ค 3end = max(3, 3) โ€” the first interval meets itself, harmless[1,3]
[2,6][1,3]2 โ‰ค 3end = max(3, 6) = 6[1,6]
[8,10][1,6]8 > 6start new block[1,6] [8,10]
[15,18][8,10]15 > 10start new block[1,6] [8,10] [15,18]
Java โ€” sort by start, extend the last block
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class MergeIntervals { public static int[][] merge(int[][] intervals) { if (intervals.length <= 1) return intervals; Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0])); // by start List<int[]> merged = new ArrayList<>(); int[] current = intervals[0].clone(); // clone: do not mutate the input merged.add(current); for (int[] next : intervals) { if (next[0] <= current[1]) { // overlaps (touching counts) current[1] = Math.max(current[1], next[1]); } else { current = next.clone(); merged.add(current); } } return merged.toArray(new int[0][]); } public static void main(String[] args) { int[][] intervals = {{1, 3}, {8, 10}, {2, 6}, {15, 18}}; System.out.println(Arrays.deepToString(merge(intervals))); // [[1, 6], [8, 10], [15, 18]] System.out.println(Arrays.deepToString(merge(new int[][]{{1, 4}, {4, 5}}))); // [[1, 5]] } }
ApproachTimeSpaceNote
Pairwise merge until stableO(nยณ)O(n)Each pass O(nยฒ)
Sort + sweepO(n log n)O(n)Output list; sort itself uses O(log n)
Common bugs:
  • current[1] = next[1] instead of Math.max(current[1], next[1]) โ€” a nested interval like [2,3] inside [1,4] would shrink the block (Template 3 diagram).
  • Using < instead of <= for the overlap test โ€” [1,4] and [4,5] would not merge.
  • Comparator (a, b) -> a[0] - b[0] overflows for large negative/positive starts. Integer.compare is always safe.
  • Mutating the caller's arrays: extending result.get(last)[1] when last is the caller's int[] rewrites their input. Clone the arrays you store.
10
Drill 39 ยท Overlap Detection

Meeting Rooms (and the Rooms-Needed Follow-Up)

๐Ÿท๏ธ

Difficulty

Easy ยท follow-up Medium

๐Ÿ”—

LeetCode

LC 252 ยท write-up ยท LC 253

๐Ÿงฉ

Pattern

sort by start min-heap

Given meeting times [start, end), decide whether one person can attend all of them. A meeting ending at 10 and one starting at 10 do not conflict. The natural follow-up (LC 253) asks for the minimum number of rooms so that every meeting can be held.

Example
Input: [[0,30], [5,10], [15,20]] Output: false // rooms needed: 2 Input: [[7,10], [2,4]] Output: true // rooms needed: 1
Key insight: Sort by start. If meetings i < j overlap, then start[i+1] โ‰ค start[j] < end[i], so meeting i also overlaps its neighbour i + 1. Checking each meeting against the one just before it is therefore enough for the yes/no question. Counting rooms needs more state, because a long earlier meeting may still be running: keep a min-heap of the end times of meetings in progress โ€” its top says whether any room has freed up.

Brute force โ†’ optimal. Comparing every pair is O(nยฒ). For the yes/no question, sorting and checking adjacent pairs is O(n log n): if any two meetings overlap, then in sorted order some adjacent pair overlaps. For the room count, push each meeting's end time onto a min-heap; before pushing, if the earliest-ending meeting has finished (peek() <= start), reuse its room by polling. The heap size at the end is the peak number of simultaneous meetings.

[0,30], [5,10], [15,20] on a timeline โ€” two rooms are enough
conflict 5โ€“10 [0,30] [5,10] [15,20] 5 < 30 โ†’ can't attend all room 1 room 2 freed at 10 0 5 10 15 20 25 30
Walkthrough โ€” min-heap of end times (LC 253)
meetingheap beforepeek โ‰ค start?actionheap after
[0,30][]โ€”push 30[30]
[5,10][30]30 โ‰ค 5? nopush 10 (new room)[10, 30]
[15,20][10, 30]10 โ‰ค 15? yespoll 10, push 20 (reuse)[20, 30]
โ€”โ€”โ€”answer = heap size2
Java โ€” adjacent check, plus the rooms follow-up
import java.util.Arrays; import java.util.PriorityQueue; public class MeetingRooms { // LC 252: can one person attend every meeting? public static boolean canAttendMeetings(int[][] intervals) { Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0])); // no a[0]-b[0] overflow for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] < intervals[i - 1][1]) return false; // starts before prev ends } return true; } // LC 253 follow-up: how many rooms are needed? public static int minMeetingRooms(int[][] intervals) { Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0])); PriorityQueue<Integer> endTimes = new PriorityQueue<>(); // min-heap of end times for (int[] meeting : intervals) { if (!endTimes.isEmpty() && endTimes.peek() <= meeting[0]) { endTimes.poll(); // reuse the freed room } endTimes.offer(meeting[1]); } return endTimes.size(); } public static void main(String[] args) { int[][] busy = {{0, 30}, {5, 10}, {15, 20}}; System.out.println(canAttendMeetings(busy)); // false System.out.println(canAttendMeetings(new int[][]{{7, 10}, {2, 4}})); // true System.out.println(minMeetingRooms(busy)); // 2 } }
ApproachTimeSpaceNote
All pairs overlap testO(nยฒ)O(1)Answers both questions slowly
Sort + adjacent check (LC 252)O(n log n)O(log n)Sort's stack; not O(1) as often claimed
Sort + min-heap (LC 253)O(n log n)O(n)Heap holds active meetings
Two sorted arrays of starts/endsO(n log n)O(n)Event sweep, no heap
Common bugs (one was in the original version of this drill):
  • Comparator (a, b) -> a[0] - b[0] โ€” overflows for extreme values and sorts wrongly. Use Integer.compare(a[0], b[0]).
  • <= instead of < in the conflict test: back-to-back meetings [5,10] and [10,15] do not conflict.
  • In the rooms version, sorting by end time instead of start time โ€” the heap logic assumes meetings arrive in start order.
  • Claiming O(1) space: Arrays.sort on objects (int[][]) is a merge sort (TimSort) that may allocate up to n/2 references.
11
Chapter Eleven ยท Summary

All Seven Drills at a Glance

#DrillPatternTimeSpaceDifficulty
33Search Insert PositionLower bound, half-openO(log n)O(1)Easy
34Find Peak ElementConverge on slopeO(log n)O(1)Medium
35Find Minimum in Rotated ArrayConverge, compare with hiO(log n)O(1)Medium
36Search a 2D Sorted MatrixStaircase from top-rightO(m + n)O(1)Medium
37Median of Two Sorted ArraysBinary search on partitionO(log min(m, n))O(1)Hard
38Merge IntervalsSort by start + sweepO(n log n)O(n)Medium
39Meeting RoomsSort + adjacent check (heap for rooms)O(n log n)O(log n) / O(n)Easy
Before writing any binary search, say out loud:
  • What is the yes/no question, and is it false-then-true over the index?
  • Is the range [lo, hi) or [lo, hi], and what does the loop return when it ends?
  • Which update keeps mid as a candidate โ€” and does mid round the right way for it?
What to practice next

โ† 03 Strings & Sliding Window All drills 05 Linked Lists โ†’