Skip to main content
CodeOath
← All posts

Data Structures & Algorithms105 min total · 16 parts

DSA Patterns for Coding Interviews: The Techniques Behind Almost Every Problem

Part 8 of 16 · ~3 min

Binary Search

Kept as a flat array sorted by course code for reporting, the catalog needs a fast lookup:

function findCourseByCode(sortedCatalog, code) {
  let lo = 0, hi = sortedCatalog.length - 1;
  while (lo <= hi) {
    const mid = lo + Math.floor((hi - lo) / 2);   // written this way to avoid overflow elsewhere
    if (sortedCatalog[mid].code === code) return sortedCatalog[mid];
    if (sortedCatalog[mid].code < code) lo = mid + 1;   // target's further right — drop the left half
    else hi = mid - 1;                                   // target's further left — drop the right half
  }
  return null;
}

Halving the remaining space every comparison is the source of O(log n) — 42,000 items take about sixteen halvings to whittle down to one. Bugs here are almost never about the logic; they're off-by-one slips in lo <= hi vs. lo < hi, or mid ± 1 vs. bare mid. Write hi = mid on the "too big" branch without ever nudging toward hi = mid - 1 and you've built an infinite loop that keeps re-checking the same midpoint. If a binary search seems to hang forever, shrink the input down to two elements and step through it by hand — the missing -1 or +1 almost always surfaces right there.

Binary search on the answer, not on an array

A different-looking version of the same tool searches a range of possible answers instead of an array. Pick a candidate, write a feasibility check that flips exactly once as the candidate changes, and binary-search for the flip point directly.

Recognize it by: phrasing like "minimize the largest," or "smallest value for which this holds," where checking every possible answer one by one would eventually work but is slow, and checking a single candidate is cheap.

A degree's required courses come in a fixed order — prerequisites forbid reshuffling — but the registrar can still choose where semester boundaries fall. Given required-course credit loads in order and a fixed number of semesters, what's the lightest the heaviest semester can possibly be made?

function fitsInKSemesters(creditLoads, maxPerSemester, numSemesters) {
  let semesters = 1, currentLoad = 0;
  for (const credits of creditLoads) {
    if (currentLoad + credits > maxPerSemester) {   // this course needs a fresh semester
      semesters++;
      currentLoad = 0;
    }
    currentLoad += credits;
  }
  return semesters <= numSemesters;
}

function minimizeHeaviestSemester(creditLoads, numSemesters) {
  let lo = Math.max(...creditLoads);                         // one enormous course alone sets the floor
  let hi = creditLoads.reduce((a, b) => a + b, 0);            // everything crammed into one sets the ceiling

  while (lo < hi) {
    const mid = lo + Math.floor((hi - lo) / 2);   // candidate cap on credits per semester
    if (fitsInKSemesters(creditLoads, mid, numSemesters)) {
      hi = mid;                     // this cap works — see if a tighter one still does
    } else {
      lo = mid + 1;                 // too tight to fit the required number of semesters
    }
  }
  return lo;
}

The obvious first move is to try every possible cap starting from lo and stop at the first one that works — and that costs O(n · range), with range potentially as large as the degree's entire credit total. But look at what's actually being narrowed down here: not a position in an array, a number. fitsInKSemesters is standing in for the equality test from ordinary binary search, just answering yes-or-no instead of found-or-not-found. Pick one candidate cap and step through fitsInKSemesters by hand — tally credits, and the moment adding the next course would breach the cap, seal off the current semester and start counting a new one — and something falls out of that trace: pushing the cap higher can never force more semesters, only the same number or fewer. That one-directional relationship between the candidate and the outcome is what licenses binary search at all, and spotting it is what shrinks an O(n · range) brute force down to O(n · log(range)).

Binary Search on Code Lab has the practice set.