INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL
01
00/11
01 / COVER STEP 04 · BINARY SEARCH
INVARIANT · STEP 04 · DECK 1 OF 2
ELEVEN SEARCHES ONE COLLAPSE

Every one of these is the same move: keep a live interval [lo, hi] and throw away half of it each step, until one element remains. What changes from problem to problem is only the question you ask at mid - and once you see that, eleven problems become one.

11Problems
6Patterns
10Units
11Lectures
← → ↑ ↓  or  W A S D  navigate SPACE next   DOUBLE-CLICK to advance I index   G goto problem   P predict H hide solutions   T close video   F fullscreen
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

This is not a list of problems. It is 10 learning units, and every one runs the same four beats. Go in order and the problems stop feeling random.

01 INTROWhat the concept is, and what to watch for
02 VIDEOThe lecture, full-width in theatre mode
03 DRILLS2–4 questions checking the lecture landed
04 PROBLEMSThe sheet problems that concept unlocks

Moving around

← ↑Back a slide — or A / W → ↓Forward — or D / S / Space 2×clickDouble-click the right side to advance, left to go back. A single click never moves the deck. IThe index: every problem, clickable, with your progress GJump straight to a problem by its number FFullscreen

While you study

HHide solutions — blurs code and steps so you try first PPredict mode: call the next step before the animation plays it TClose the video — Esc works too ☐ ★Mark solved, or star to revisit. Both are saved automatically.

11 PROBLEMS · EVERY ONE LINKS TO LEETCODE TO SOLVE

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 11 PROBLEMS

Click any problem to jump straight to it. Solved ones turn green. Press I from anywhere to come back here. The number is the sheet's row, not your position — the deck teaches in the order that builds, so you will meet them slightly out of numerical sequence.

BINARY SEARCH ON 1D ARRAYS · 08
BINARY SEARCH ON 2D ARRAYS · 03
SOLVED HAS A LEETCODE LINK
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

WHICH BINARY SEARCH, AND WHY

Every one of these is the same collapse of an interval. What the cards below name is the question you ask at mid — and hearing which question a statement is asking for is the entire skill.

A SORTED ARRAY AND “FIND / SEARCH / POSITION OF”

sorted is the invitation; one comparison discards half

PLAIN BINARY SEARCH — a[mid] vs targetO(log n) time · O(1) space
“FIRST / LAST / INSERT POSITION”, OR COUNT OF A VALUE

you need a boundary, not just any match

lower_bound / upper_bound — store and keep goingO(log n) time · O(1) space
“ROTATED SORTED ARRAY”

not globally sorted, but one half always is

IDENTIFY THE SORTED HALF, then discard the otherO(log n) · O(n) if duplicates
“EVERY ELEMENT TWICE EXCEPT ONE”, SORTED

the pairing's parity breaks exactly at the answer

BINARY SEARCH ON PAIR PARITYO(log n) time · O(1) space
“A PEAK” / “LOCAL MAXIMUM” — NOT NECESSARILY SORTED

a[mid] vs a[mid+1] gives a direction even without order

FOLLOW THE SLOPE UPHILLO(log n) time · O(1) space
A ROW- AND COLUMN-SORTED MATRIX

2D sortedness is 1D sortedness in disguise, or a staircase

FLATTEN TO 1D, or walk from a cornerO(log mn) or O(m+n)
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
05 / CONSTRAINTS BEFORE YOU WRITE A LINE

READ THE ANSWER OFF THE CONSTRAINTS

Binary search is what you reach for when the bound is enormous and the structure is sorted. Type a value for n and the row that survives lights up.

n ≤
BUDGET
WHAT THAT BUYS YOU
10–12
O(n!)
permutations — never a binary-search shape
20–25
O(2ⁿ)
subset enumeration
10³
O(n²)
the nested-loop brute force these problems beat
10⁵
O(n log n)
sort first, then binary search — or search directly
10⁶–10⁹
O(log n)
binary search on a sorted structure — the whole deck
up to 10¹⁸
O(log n)
binary search the ANSWER RANGE (deck 2) reaches here

THE GOLD-EDGED ROWS ARE WHERE THIS DECK LIVES · LINEAR DIES LONG BEFORE log n DOES · DECK 2 PUSHES THE RANGE TO 10¹⁸

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
06 / ROADMAP WHAT YOU ARE ABOUT TO DO

THE WHOLE RUN, 10 UNITS

All 10 units, one collapse. Seven search a 1D array; three carry the idea into a matrix. The answer-space reframe — sqrt, koko, split array, median — is deck 2.

UNIT 01

The Binary Search Invariant

▶ 33:273 DRILLS1 PROBLEM
UNIT 02

Lower Bound & Upper Bound

▶ 32:263 DRILLS1 PROBLEM
UNIT 03

First & Last Occurrence

▶ 25:283 DRILLS1 PROBLEM
UNIT 04

Search a Rotated Array

▶ 16:383 DRILLS2 PROBLEMS
UNIT 05

Minimum in a Rotated Array

▶ 17:083 DRILLS1 PROBLEM
UNIT 06

Single Element by Parity

▶ 22:163 DRILLS1 PROBLEM
UNIT 07

Find a Peak

▶ 32:533 DRILLS1 PROBLEM
UNIT 08

Flatten the Grid

▶ 15:423 DRILLS1 PROBLEM
UNIT 09

The Staircase Walk

▶ 15:293 DRILLS1 PROBLEM
UNIT 10

A Peak in Two Dimensions

▶ 20:023 DRILLS1 PROBLEM
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
07 / WARMUP LOAD THE INVARIANT BEFORE UNIT 01 · 1 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · RECALL

Why write mid = lo + (hi − lo) / 2 instead of the obvious mid = (lo + hi) / 2?

Overflow. When lo and hi are both near INT_MAX — which happens in binary-search-on-answers with large bounds — lo + hi wraps past 2³¹ to a negative number, and mid becomes a garbage index that crashes or reads out of bounds. lo + (hi − lo) / 2 never forms the oversized sum yet computes the same midpoint. It is the most famous bug in binary search — it lived in the JDK's own library for nearly a decade — and writing it the safe way costs nothing.

DRILL 02 · RECALL

Given n ≤ 10⁹ and a sorted array, what target complexity should you aim for before writing anything?

O(log n). At 10⁹ even a single linear pass is on the edge, and the word sorted is the tell: it is the precondition binary search needs. Reading “sorted” plus a large bound and reaching for log n before you have thought about the specific problem is exactly the constraint-first habit this deck drills — the target complexity is often decidable from the input size alone. The underlying budget is the same one Sorting (step 02) and Arrays 1 (step 03) derive: about 10⁸ operations a second. Only the bound changes.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
08 / WARMUP LOAD THE INVARIANT BEFORE UNIT 01 · 2 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · BUG

This binary search loops forever on some inputs. Which line causes it?

while (lo < hi) {
    int mid = lo + (hi - lo) / 2;
    if (a[mid] < t) lo = mid + 1;
    else hi = mid;                     // target could be at mid
}

An infinite loop from an interval that stops shrinking. Once hi = lo + 1, mid = lo + (1)/2 = lomid is pinned to lo. Pairing hi = mid with lo = mid then moves neither bound: whichever branch runs, the interval is unchanged and the next iteration computes the same mid forever. The rule: whichever side keeps mid, the other must step past it — one of the two updates is always mid ± 1. Writing lo = mid and hi = mid in the same loop is how binary searches hang.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
09 / INTRO UNIT 01 · The Binary Search Invariant

UNIT 01 — The Binary Search Invariant

This is the whole topic in one move, and everything else in both decks is a variation on it. Keep a live interval [lo, hi] that is guaranteed to contain the answer if it exists. Look at the middle; because the array is sorted, one comparison tells you which half the answer cannot be in, and you throw that half away. Repeat, and the interval collapses from n elements to one in about log₂ n steps.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND A VALUE IN A SORTED ARRAY WITHOUT LOOKING AT MOST OF IT?

INVARIANT[lo, hi]midDISCARD A HALFO(log n)
WHAT TO WATCH FOR
  • 01THE INVARIANT: THE ANSWER, IF PRESENT, IS ALWAYS INSIDE [lo, hi]
  • 02WRITE mid = lo + (hi−lo)/2, NOT (lo+hi)/2 — THE LATTER OVERFLOWS
  • 03THE LOOP IS lo <= hi, AND EACH BRANCH MOVES A BOUND PAST mid (mid ± 1)
  • 04log₂(10⁹) IS ONLY ABOUT 30 STEPS — THAT IS THE ENTIRE POINT
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
10 / VIDEO UNIT 01 · The Binary Search Invariant

BS-1. Binary Search Introduction

STRIVER A2Z
The Binary Search Invariant
RUNTIME 33:27
AFTER THIS → 3 DRILLS · PROBLEM #01
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
11 / DRILL UNIT 01 · The Binary Search Invariant · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What is the loop invariant that makes binary search correct — the property true before every iteration?

The answer, if it exists, is inside [lo, hi]. That single sentence is what every step must preserve, and it is why discarding a half is safe: you only ever throw away elements you have proven cannot be the answer. Sortedness is what lets one comparison do that proving, but the invariant is the thing you reason about. Every binary-search bug is ultimately a step that violates it — discarding a half that could still contain the answer, or failing to shrink and looping forever. State the invariant and the code writes itself.

DRILL 02 · TRACE

On [3, 8, 12, 19, 24, 31, 42, 55] searching for 42, what is mid at the very first step, and which half is discarded?

mid = (0+7)/2 = 3, so a[mid] = 19. Since 19 < 42, the target must be to the right — everything at index 3 or below is too small — so lo jumps to 4 and the left half vanishes. The search then finds 42 at index 6 two steps later. Watch the MECHANISM slide strike out the discarded half each step; in PREDICT mode it asks you which half survives at every comparison.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
12 / DRILL UNIT 01 · The Binary Search Invariant · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This returns -1 for a target that IS in the array. Which line is the bug?

while (lo <= hi) {
    int mid = lo + (hi - lo) / 2;
    if (a[mid] == t) return mid;
    if (a[mid] < t) hi = mid - 1;
    else lo = mid + 1;
}

The branches are backwards. When a[mid] < t, the middle value is too small, so the answer lies to the right — you must raise lo to mid + 1, not lower hi. As written it discards the half that actually contains the target and searches the wrong side, returning -1. It even works by luck when the target happens to sit at the first mid. Tie the direction to the invariant every time: too small ⇒ go right, too big ⇒ go left, and the branches cannot be swapped.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
13 / MECHANISM UNIT 01 · BSEARCH · CODE MIRRORED

KEEP AN INTERVAL, THROW AWAY HALF OF IT

The one invariant behind the whole topic: the answer, if it exists, is always inside [lo, hi]. Look at the middle, and because the array is sorted, one comparison rules out an entire half. Watch the eliminated side strike through — that collapse from n to 1 in log n steps is the entire idea. Note lo + (hi−lo)/2, not (lo+hi)/2: the latter can overflow on large indices.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
14 / PROBLEM #01 · CORE · EASY

Binary Search

EASY core ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

The two words that matter are sorted and O(log n) — the constraint (often n ≤ 10⁵ or far more) rules out a linear scan, and sortedness is the precondition binary search needs. When you see a sorted array and a target to locate, this is the default, not a clever trick to search for.

INTUITION

Maintain [lo, hi] guaranteed to contain the target if present. Look at the middle: if it equals the target you are done; if it is too small the answer is strictly to the right, so raise lo; if too big, lower hi. Each step halves the interval, so it collapses to empty (target absent) or a match in about log₂ n steps.

STEPS
  1. Set lo = 0, hi = n − 1 — the interval is the whole array
  2. While lo ≤ hi, compute mid = lo + (hi − lo) / 2
  3. If a[mid] == target, return mid
  4. If a[mid] < target, the answer is right: lo = mid + 1
  5. If a[mid] > target, the answer is left: hi = mid − 1
  6. If the loop ends, lo > hi and the target is absent: return −1
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// The invariant: the target, if present, is always inside [lo, hi].
// One comparison at the middle discards a whole half, because the array
// is sorted. mid = lo + (hi-lo)/2 avoids the (lo+hi) overflow.
int search(vector<int>& a, int t) {
    int lo = 0, hi = a.size() - 1;

    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == t) return mid;
        if (a[mid] < t) lo = mid + 1;       // too small: answer is right
        else            hi = mid - 1;       // too big: answer is left
    }
    return -1;
}
TIMEO(log n)each step discards half of the remaining interval
SPACEO(1)two integer indices; iterative, so no call stack
TRAP

Swapping the two branches — moving hi when the middle is too small — searches the wrong half and returns -1 for present targets. Tie the direction to the meaning: too small means go right. The other classic is mid = (lo + hi) / 2, which overflows once indices get large (as they do in deck 2's answer-space searches); write lo + (hi − lo) / 2 from the start so the habit is already there when it matters.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #01 · BINARY SEARCH

BS-1. Binary Search Introduction

The walkthrough for #01 Binary Search. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-1. Binary Search Introduction
RUNTIME 33:27
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
15 / INTRO UNIT 02 · Lower Bound & Upper Bound

UNIT 02 — Lower Bound & Upper Bound

The single most reusable primitive in the topic. lower_bound finds the first index with a[i] ≥ target; upper_bound the first with a[i] > target. The trick that separates them from plain search is what you do on a candidate: you do not stop — you record the index and keep searching left for an even earlier one. Search Insert Position is literally lower_bound, and counts, ranges and floors all fall out of these two.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND WHERE A VALUE BELONGS, EVEN WHEN IT IS NOT PRESENT?

lower_boundupper_boundCANDIDATEINSERT POSITIONCOUNT
WHAT TO WATCH FOR
  • 01ON A QUALIFYING mid YOU STORE IT AND GO LEFT (hi = mid-1) — YOU DO NOT RETURN
  • 02THE ANSWER STARTS AT n, MEANING 'EVERYTHING IS SMALLER, INSERT AT THE END'
  • 03lower_bound's RETURNED INDEX IS ALSO THE COUNT OF ELEMENTS STRICTLY LESS THAN target
  • 04upper_bound IS THE SAME CODE WITH > INSTEAD OF >=
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
16 / VIDEO UNIT 02 · Lower Bound & Upper Bound

BS-2. Implement Lower Bound and Upper Bound

STRIVER A2Z
Lower Bound & Upper Bound
RUNTIME 32:26
AFTER THIS → 3 DRILLS · PROBLEM #02
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
17 / DRILL UNIT 02 · Lower Bound & Upper Bound · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What distinguishes lower_bound from a plain binary search that returns on a match?

It refuses to stop early. Plain search returns the moment a[mid] qualifies; lower_bound instead saves mid as the best-so-far and continues into the left half looking for an earlier qualifier. That “store and keep going” is the whole difference, and it is why lower_bound gives you the first index with a[i] ≥ t rather than an arbitrary one. Everything else — insert position, counts, floor and ceil — is a thin wrapper over this one behaviour.

DRILL 02 · TRACE

On [1, 3, 3, 5, 8, 8, 8, 11], what does lower_bound(8) return, and what would upper_bound(8) return?

lower_bound(8) = 4 (the first 8, at index 4) and upper_bound(8) = 7 (the first element strictly greater than 8, the 11 at index 7). Their difference, 7 − 4 = 3, is exactly the number of 8s — which is how you count occurrences in O(log n) with no scan. Search Insert for 8 would also return 4, since that is where an 8 belongs. The MECHANISM slide shows the candidate marker sliding left as earlier 8s are found.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
18 / DRILL UNIT 02 · Lower Bound & Upper Bound · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You need the floor of a target (largest element ≤ target) in a sorted array. How does it relate to these primitives?

floor sits at upper_bound(target) − 1. upper_bound gives the first element strictly greater than the target, so the element just before it is the largest one that is ≤ target — the floor (guarding for index 0, meaning no floor exists). Ceil is the mirror: it is lower_bound(target) directly. Once you see floor and ceil as ±1 offsets of these two bounds, a whole class of “nearest value” problems becomes two-line wrappers instead of fresh searches.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
19 / MECHANISM UNIT 02 · LOWERBOUND · CODE MIRRORED

DO NOT STOP AT A MATCH — STORE IT AND KEEP GOING

The primitive behind half of this deck. lower_bound finds the first index with a[i] ≥ target, so on a hit it does not return — it records the candidate and searches further left for an even earlier one. Start the answer at n (“not found”). Search Insert Position is exactly this, and upper_bound is the same with >.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
20 / PROBLEM #02 · BOUNDARY · EASY

Search Insert Position

EASY boundary ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Return the index where the target is, or would be inserted” in a sorted array. “Would be inserted” is the giveaway that a plain find-or-fail search is not enough — you need the position even when the target is absent, which is precisely lower_bound.

INTUITION

Run lower_bound: the first index i with a[i] ≥ target. On a qualifying mid, store it and search left for an earlier one; otherwise search right. Whatever candidate survives is where the target belongs — if it is present, that is its index; if not, that is where it would slot in to keep the array sorted. Start the answer at n so an all-smaller array inserts at the end.

STEPS
  1. Set lo = 0, hi = n − 1, and ans = n (insert at the end by default)
  2. While lo ≤ hi, take mid = lo + (hi − lo) / 2
  3. If a[mid] ≥ target, record ans = mid and search left: hi = mid − 1
  4. Otherwise a[mid] is too small, so search right: lo = mid + 1
  5. Return ans — the first index ≥ target, which is the insert position
  6. This same value is the count of elements strictly less than the target
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// Search Insert Position IS lower_bound: the first index with a[i] >= t.
// On a qualifying mid, store it and keep looking LEFT for an earlier one.
int searchInsert(vector<int>& a, int t) {
    int lo = 0, hi = a.size() - 1, ans = a.size();

    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] >= t) { ans = mid; hi = mid - 1; }   // store, keep left
        else lo = mid + 1;
    }
    return ans;
}
TIMEO(log n)one binary search; each step halves the interval
SPACEO(1)a couple of indices, iterative
TRAP

Returning on the first a[mid] == target gives an occurrence, not the insert position, and — worse — not even the first occurrence when there are duplicates. Always store and keep searching left. The mirror mistake is initialising ans = -1 or n − 1: when every element is smaller than the target the answer is n (append at the end), so that must be the default the search falls back to.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #02 · SEARCH INSERT POSITION

BS-2. Implement Lower Bound and Upper Bound

The walkthrough for #02 Search Insert Position. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-2. Implement Lower Bound and Upper Bound
RUNTIME 32:26
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
21 / INTRO UNIT 03 · First & Last Occurrence

UNIT 03 — First & Last Occurrence

Plain binary search finds an occurrence of a value; this problem wants the first and the last. Both are boundary searches: for the first, on a match you record and keep going left; for the last, you keep going right. Equivalently the first occurrence is lower_bound(t) and the last is lower_bound(t+1) − 1 — so the whole problem is two calls to the primitive you just learned, and the count of occurrences is their difference plus one.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE FULL RANGE OF A REPEATED VALUE IN O(log n)?

FIRST OCCURRENCELAST OCCURRENCEBOUNDARY BIASRANGECOUNT = last-first+1
WHAT TO WATCH FOR
  • 01A MATCH IS NOT A STOP — RECORD IT AND KEEP NARROWING TOWARD THE BOUNDARY YOU WANT
  • 02FIRST BIASES LEFT (hi = mid-1); LAST BIASES RIGHT (lo = mid+1)
  • 03FIRST = lower_bound(t); LAST = lower_bound(t+1) - 1 — TWO CALLS, NO NEW IDEA
  • 04IF lower_bound(t) IS PAST THE END OR a[it] != t, THE VALUE IS ABSENT: RETURN [-1,-1]
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
22 / VIDEO UNIT 03 · First & Last Occurrence

BS-3. First and Last Occurrences in Array

STRIVER A2Z
First & Last Occurrence
RUNTIME 25:28
AFTER THIS → 3 DRILLS · PROBLEM #03
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
23 / DRILL UNIT 03 · First & Last Occurrence · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

To find the LAST occurrence of a value, what do you do when a[mid] equals the target?

Record and go right. Finding the last occurrence is the mirror of finding the first: on a match you still refuse to stop, but now you bias toward the right half, saving mid and setting lo = mid + 1 to hunt for a later match. First-occurrence biases left, last biases right, and both are the “store, keep going” move from lower_bound. Two nearly identical searches bracket the value's range.

DRILL 02 · TRACE

On [2, 4, 4, 4, 4, 6, 7, 9] searching for 4, the first-occurrence pass finds index 1. How many 4s are there, using the boundary trick?

Four. The first 4 is at index 1 and the last is at index 4, so the count is last − first + 1 = 4 − 1 + 1 = 4. Equivalently, lower_bound(4) = 1 and lower_bound(5) = 5, and 5 − 1 = 4. Both routes give the same answer with two O(log n) searches and no counting scan — the payoff of treating occurrence-counting as a pair of boundary searches.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
24 / DRILL UNIT 03 · First & Last Occurrence · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This find-first returns index 3 for the first 4 in [2,4,4,4,4,6], when it should return 1. Which line is wrong?

if (a[mid] == t) return mid;
else if (a[mid] < t) lo = mid + 1;
else hi = mid - 1;

Returning on the first match abandons the boundary search. On [2,4,4,4,4,6] the first mid is index 2 (a 4), and returning it gives 2 — or with different sizes, 3 — never guaranteed to be the leftmost 4. To find the first, replace return mid with “record mid, then hi = mid − 1” so the search continues into the left half. This is the exact mistake the unit's concept warns about: a match is the middle of the search, not the end.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
25 / MECHANISM UNIT 03 · FIRSTLAST · CODE MIRRORED

A MATCH IS THE MIDDLE OF THE SEARCH, NOT THE END

Plain binary search returns any occurrence; this problem needs the first and the last. So on a match you record the index and keep narrowing toward it — left for the first, right for the last. Two lower-bound-style passes, or lower_bound(t) and lower_bound(t+1)−1. Stopping early is the mistake this whole unit exists to prevent.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
26 / PROBLEM #03 · BOUNDARY · MED

Find First and Last Position

MED boundary ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Find the first and last position” of a value, or “count occurrences”, in a sorted array, in O(log n). The plural — a range rather than a single index — plus the log bound is the signal for two boundary searches, not one plain search that stops at any match.

INTUITION

Run lower_bound twice. The first occurrence is lower_bound(target): the first index with a[i] ≥ target (verify it actually equals the target, else the value is absent). The last occurrence is lower_bound(target + 1) − 1: one step before where values first exceed the target. The count is their difference plus one. Equivalently, bias one search left on a match and the other right.

STEPS
  1. first = lower_bound(target); if first == n or a[first] != target, return [-1, -1]
  2. last = lower_bound(target + 1) − 1
  3. Return [first, last]; the count is last − first + 1
  4. Alternatively, run two searches that both continue past a match
  5. The first-occurrence search records mid then goes left (hi = mid − 1)
  6. The last-occurrence search records mid then goes right (lo = mid + 1)
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// Two boundary searches. First occurrence biases LEFT on a match;
// last occurrence biases RIGHT. The value's range is [first, last], and
// the count is last - first + 1.
vector<int> searchRange(vector<int>& a, int t) {
    auto bound = [&](int t) {                    // lower_bound(t)
        int lo = 0, hi = a.size() - 1, ans = a.size();
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (a[mid] >= t) { ans = mid; hi = mid - 1; }
            else lo = mid + 1;
        }
        return ans;
    };
    int first = bound(t);
    if (first == (int)a.size() || a[first] != t) return {-1, -1};
    return {first, bound(t + 1) - 1};
}
TIMEO(log n)two binary searches, each O(log n)
SPACEO(1)a handful of indices
TRAP

Finding one occurrence and then scanning outward for the range is O(n) in the worst case — an array that is all the same value degrades the “scan to the edges” step to linear, defeating the point. Both boundaries must be found by binary search. The other trap is forgetting the presence check: lower_bound(target) returns a valid index even when the target is absent (it points at the next-larger element), so you must confirm a[first] == target before trusting the range.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #03 · FIND FIRST AND LAST POSITION

BS-3. First and Last Occurrences in Array

The walkthrough for #03 Find First and Last Position. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-3. First and Last Occurrences in Array
RUNTIME 25:28
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
27 / INTRO UNIT 04 · Search a Rotated Array

UNIT 04 — Search a Rotated Array

A sorted array rotated at an unknown pivot is no longer globally sorted — but the saving insight is that at any mid, at least one of the two halves is still perfectly sorted. Compare a[lo] to a[mid] to find which half that is; then check whether the target lies within that sorted half's known range. If it does, search there; if not, the target must be in the other half. Duplicates (Search II) are the one wrinkle: they can make the sorted half impossible to identify.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU BINARY-SEARCH AN ARRAY THAT IS SORTED BUT ROTATED?

ROTATION PIVOTSORTED HALFRANGE CHECKDUPLICATESO(n) WORST CASE
WHAT TO WATCH FOR
  • 01a[lo] <= a[mid] MEANS THE LEFT HALF IS THE SORTED ONE; OTHERWISE IT IS THE RIGHT
  • 02CHECK IF THE TARGET LIES INSIDE THE SORTED HALF'S RANGE — THAT DECIDES THE DISCARD
  • 03SEARCH II: WHEN a[lo]==a[mid]==a[hi] YOU CANNOT TELL — SHRINK BOTH ENDS, lo++ hi--
  • 04THAT DUPLICATES CASE IS WHAT BREAKS THE WORST CASE FROM O(log n) TO O(n)
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
28 / VIDEO UNIT 04 · Search a Rotated Array

BS-4. Search Element in Rotated Sorted Array - I

STRIVER A2Z
Search a Rotated Array
RUNTIME 16:38
AFTER THIS → 3 DRILLS · PROBLEM #04, #05
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
29 / DRILL UNIT 04 · Search a Rotated Array · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

At any mid in a rotated sorted array, how do you tell which half is the sorted one?

a[lo] ≤ a[mid] means the left half is sorted. If the value at the low end is not greater than the value at the middle, then [lo..mid] contains no rotation drop and is in order; otherwise the drop is in the left half, which forces the right half [mid..hi] to be the sorted one. Exactly one half always qualifies, and identifying it is the whole move — once you know the sorted half's range, a single containment check tells you which side to discard.

DRILL 02 · TRACE

On [7, 8, 9, 1, 2, 3, 4, 5] searching for 2, the first mid is index 3 (value 1). Which half is sorted, and where does the search go?

The right half is sorted, and the target is in it. a[lo] = 7 > a[mid] = 1, so the left half holds the drop and the right half [1, 2, 3, 4, 5] is the sorted one. The target 2 lies between a[mid] = 1 and a[hi] = 5, so it must be in that sorted right half — lo jumps to mid + 1. Two steps later it lands on 2. In PREDICT mode the MECHANISM slide asks you, at each mid, whether the target lies in the sorted half.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
30 / DRILL UNIT 04 · Search a Rotated Array · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Why does adding duplicates (Search II) break the O(log n) guarantee?

The identification step fails. The whole method rests on comparing a[lo] and a[mid] to find the sorted half — but when a[lo] == a[mid] == a[hi] (e.g. [3, 1, 3, 3, 3]), that comparison is uninformative: the sorted half could be either side. The only safe move is to nibble one element off each end (lo++, hi--) and retry, and in the worst case — an array of all-but-one identical values — that is O(n). Search II is Search I plus this one guard, which the next slide shows as a one-line diff.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
31 / MECHANISM UNIT 04 · ROTATED · CODE MIRRORED

ONE HALF IS ALWAYS STILL SORTED — FIND IT

A rotated array is not sorted, but at any mid at least one of [lo..mid] and [mid..hi] still is. Compare a[lo] to a[mid] to identify the sorted half, check whether the target falls inside its known range, and discard the half that provably cannot hold it. Everything hard about rotation reduces to that one identification.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
32 / PROBLEM #04 · ROTATED · MED

Search in Rotated Sorted Array

MED rotated ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Rotated sorted array” with a target to find, in O(log n). The word rotated is the whole signal: the array is almost sorted, and the log bound insists you exploit that rather than scan. The move is to find the half that is still sorted at each step.

INTUITION

At any mid, compare a[lo] to a[mid] to identify the sorted half. If the left half is sorted and the target lies within [a[lo], a[mid]), search left; otherwise search right. If instead the right half is sorted and the target lies within (a[mid], a[hi]], search right; otherwise left. Each step still discards a full half, so it stays logarithmic.

STEPS
  1. Set lo = 0, hi = n − 1; loop while lo ≤ hi with mid = lo + (hi − lo) / 2
  2. If a[mid] == target, return mid
  3. If a[lo] ≤ a[mid], the left half is sorted
  4. target in [a[lo], a[mid]) → hi = mid − 1, else lo = mid + 1
  5. Otherwise the right half is sorted
  6. target in (a[mid], a[hi]] → lo = mid + 1, else hi = mid − 1
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// One half is always sorted. Identify it via a[lo] vs a[mid], check
// whether the target lies in that sorted half's known range, and discard
// the half that provably cannot contain it.
int search(vector<int>& a, int t) {
    int lo = 0, hi = a.size() - 1;

    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == t) return mid;
        if (a[lo] <= a[mid]) {                   // LEFT half sorted
            if (a[lo] <= t && t < a[mid]) hi = mid - 1;
            else lo = mid + 1;
        } else {                                 // RIGHT half sorted
            if (a[mid] < t && t <= a[hi]) lo = mid + 1;
            else hi = mid - 1;
        }
    }
    return -1;
}
TIMEO(log n)one sorted half is identified and half discarded each step
SPACEO(1)index arithmetic only
TRAP

Getting the range-inclusion boundaries wrong. The sorted-half check must use the right open/closed ends: for the sorted left half the target qualifies when a[lo] ≤ t < a[mid] (mid itself was already checked for equality), and for the sorted right half when a[mid] < t ≤ a[hi]. Flip an inclusive bound to exclusive and you occasionally discard the half that holds the target, returning -1 for a present value. Test against a rotation that puts the target adjacent to the pivot — that is where the boundary errors surface.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #04 · SEARCH IN ROTATED SORTED ARRAY

BS-4. Search Element in Rotated Sorted Array - I

The walkthrough for #04 Search in Rotated Sorted Array. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-4. Search Element in Rotated Sorted Array - I
RUNTIME 16:38
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
33 / PROBLEM #05 · ROTATED · MED

Search in Rotated Sorted Array II

MED rotated ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Identical to Search in Rotated Sorted Array, with one added phrase: “may contain duplicates”. That single change is the whole problem — duplicates can make it impossible to tell which half is sorted, so you need one extra guard, and you must accept that the worst case is no longer O(log n).

INTUITION

Everything from the no-duplicates version, plus one case at the top of the loop: when a[lo] == a[mid] == a[hi], the comparison that identifies the sorted half is useless, so you cannot decide a direction. The only safe move is to shrink both ends by one (lo++, hi--) and retry. This is what drags the worst case to O(n) — an array of nearly all identical values forces the nibbling to happen n times.

STEPS
  1. Same skeleton as Rotated I, returning a boolean this time
  2. Add, before the sorted-half logic: if a[lo] == a[mid] == a[hi], do lo++, hi--, continue
  3. Otherwise identify the sorted half exactly as before and range-check the target
  4. The ambiguous case is the only structural change
  5. Worst case degrades to O(n) when duplicates dominate
  6. Average case remains O(log n) when duplicates are sparse
BRUTEO(n)
OPTIMALO(log n) avg · O(n) worst
↕ SCROLL
bool search(vector<int>& a, int t) {
    int lo = 0, hi = a.size() - 1;

    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == t) return true;
        if (a[lo] == a[mid] && a[mid] == a[hi]) { lo++; hi--; continue; }
        if (a[lo] <= a[mid]) {                   // LEFT half sorted
            if (a[lo] <= t && t < a[mid]) hi = mid - 1;
            else lo = mid + 1;
        } else {                                 // RIGHT half sorted
            if (a[mid] < t && t <= a[hi]) lo = mid + 1;
            else hi = mid - 1;
        }
    }
    return false;
}
TIMEO(log n) avg · O(n) worstthe a[lo]==a[mid]==a[hi] nibble can run n times in the worst case
SPACEO(1)index arithmetic only
TRAP

Reusing the Rotated I code unchanged gives wrong answers on inputs like [3, 1, 2, 3, 3, 3, 3]: with a[lo] == a[mid] == a[hi] == 3, the a[lo] ≤ a[mid] test claims the left half is sorted and the target gets searched on the wrong side. The one-line guard fixes correctness. The subtler trap is claiming O(log n) in an interview — be honest that duplicates force O(n) worst case, because that awareness is part of what the follow-up problem is testing.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #05 · SEARCH IN ROTATED SORTED ARRAY II

BS-5. Search in Rotated Sorted Array II

The walkthrough for #05 Search in Rotated Sorted Array II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-5. Search in Rotated Sorted Array II
RUNTIME 12:44
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
34 / INTRO UNIT 05 · Minimum in a Rotated Array

UNIT 05 — Minimum in a Rotated Array

Finding the minimum of a rotated sorted array is binary search with no target at all — the comparison is between a[mid] and a[hi]. If a[mid] > a[hi] the rotation drop (and therefore the minimum) is strictly to the right; otherwise the right half is already sorted and the minimum is at mid or to its left. The index where the interval settles is not just the minimum — it is also how many times the array was rotated.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE SMALLEST ELEMENT — AND THE ROTATION COUNT — IN O(log n)?

NO TARGETa[mid] vs a[hi]hi = midROTATION COUNTPIVOT
WHAT TO WATCH FOR
  • 01THERE IS NO TARGET — THE DECISION IS a[mid] vs a[hi]
  • 02a[mid] > a[hi] ⇒ THE DROP IS RIGHT, SO lo = mid+1; ELSE hi = mid (KEEP mid!)
  • 03hi = mid, NEVER mid-1 — mid ITSELF MIGHT BE THE MINIMUM
  • 04THE INDEX OF THE MINIMUM IS EXACTLY THE ROTATION COUNT
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
35 / VIDEO UNIT 05 · Minimum in a Rotated Array

BS-6. Minimum in Rotated Sorted Array

STRIVER A2Z
Minimum in a Rotated Array
RUNTIME 17:08
AFTER THIS → 3 DRILLS · PROBLEM #06
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
36 / DRILL UNIT 05 · Minimum in a Rotated Array · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why compare a[mid] to a[hi] rather than to a[lo] when finding the minimum?

a[hi] disambiguates the unrotated case. Comparing against a[lo] stumbles when the current interval is already sorted (no drop), because then a[mid] ≥ a[lo] is true but tells you nothing about where the minimum is. Comparing against a[hi] is clean: a[mid] > a[hi] is true if and only if the drop lies strictly to the right of mid, and a[mid] ≤ a[hi] means the right half is sorted so the minimum is at mid or left. One comparison, no special cases.

DRILL 02 · BUG

This find-min uses hi = mid - 1 in the else branch. On [4, 5, 6, 7, 0, 1, 2, 3] it can miss the minimum. Why?

if (a[mid] > a[hi]) lo = mid + 1;
else hi = mid - 1;                   // min is here or left

hi = mid − 1 can throw away the minimum itself. The else branch fires when a[mid] ≤ a[hi], which means mid could be the minimum (the right half is sorted and starts at mid). Setting hi = mid − 1 excludes mid from the interval and you lose it. The correct shrink keeps it: hi = mid. This is the deck-wide rule — when mid might be the answer, shrink to mid, not mid ± 1 — and find-min is where it bites hardest.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
37 / DRILL UNIT 05 · Minimum in a Rotated Array · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You need to know how many times the array was rotated, not the minimum value itself. What changes?

Return the index, not the value. A right-rotation by k moves the original first element to position k, and the original minimum (which was at index 0) lands at exactly index k. So the position of the minimum is the rotation count — the same search answers both questions, you just report a different field. This is why the lecture on “how many times rotated” needs no separate unit: it is find-min reading out its index instead of its value.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
38 / MECHANISM UNIT 05 · FINDMIN · CODE MIRRORED

NO TARGET — COMPARE a[mid] TO a[hi]

There is nothing to search for, yet binary search still applies. a[mid] > a[hi] means the rotation point — and the minimum — lies strictly to the right; otherwise the minimum is at mid or left, so hi = mid (never mid−1, because mid itself might be it). The index where it settles is also how many times the array was rotated.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
39 / PROBLEM #06 · ROTATED · MED

Find Minimum in Rotated Sorted Array

MED rotated ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Find the minimum in a rotated sorted array”, O(log n), no target given. “No target” is the distinctive part: the comparison is not against a value you are looking for but between a[mid] and a[hi], to locate the rotation drop.

INTUITION

The minimum is the single point where the array “drops”. Compare a[mid] with a[hi]: if a[mid] > a[hi], the drop is strictly to the right, so the minimum is there — move lo past mid. Otherwise the right half is sorted with no drop, so the minimum is at mid or to its left — set hi = mid, keeping mid because it might be the answer. When the interval collapses, lo points at the minimum, and that index is the rotation count.

STEPS
  1. Set lo = 0, hi = n − 1; loop while lo < hi
  2. mid = lo + (hi − lo) / 2
  3. If a[mid] > a[hi], the drop is to the right: lo = mid + 1
  4. Else the minimum is at mid or left: hi = mid (NOT mid − 1)
  5. When lo == hi, a[lo] is the minimum
  6. The index lo is also how many times the array was rotated
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// No target: compare a[mid] to a[hi] to locate the rotation drop.
// hi = mid, never mid-1 -- mid itself might be the minimum.
int findMin(vector<int>& a) {
    int lo = 0, hi = a.size() - 1;

    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] > a[hi]) lo = mid + 1;        // drop is to the right
        else hi = mid;                           // min is mid or left
    }
    return a[lo];                                // lo is also the rotation count
}
TIMEO(log n)each step discards a half based on where the drop is
SPACEO(1)two indices
TRAP

Writing hi = mid − 1 in the else branch can discard the minimum itself, since that branch fires precisely when mid might be it. The shrink must be hi = mid, paired with a lo < hi loop so it still terminates. The second trap is comparing a[mid] to a[lo] instead of a[hi]: that comparison is ambiguous on an already-sorted (unrotated) interval, whereas a[hi] always gives a clean signal.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #06 · FIND MINIMUM IN ROTATED SORTED ARRAY

BS-6. Minimum in Rotated Sorted Array

The walkthrough for #06 Find Minimum in Rotated Sorted Array. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-6. Minimum in Rotated Sorted Array
RUNTIME 17:08
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
40 / INTRO UNIT 06 · Single Element by Parity

UNIT 06 — Single Element by Parity

In a sorted array where every element appears exactly twice except one, you can find the lone element in O(log n) by binary-searching on parity. Before the single element, each pair occupies an even-then-odd index (0-1, 2-3, ...); after it, the alignment shifts to odd-then-even. So at any mid, align it to its pair's even index and ask whether a[mid] == a[mid+1]: if the pairing is intact, the break is to the right; if not, it is here or to the left.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE ONE UNPAIRED ELEMENT IN O(log n), NOT O(n)?

PARITYPAIR ALIGNMENTEVEN INDEXO(log n)vs XOR O(n)
WHAT TO WATCH FOR
  • 01ALIGN mid TO EVEN (if mid is odd, mid--) SO YOU ALWAYS COMPARE A PAIR'S FIRST HALF
  • 02a[mid]==a[mid+1] (PAIR INTACT) ⇒ THE SINGLE IS RIGHT: lo = mid+2
  • 03PAIR BROKEN ⇒ THE SINGLE IS AT mid OR LEFT: hi = mid
  • 04THE XOR-THE-WHOLE-ARRAY TRICK IS O(n); THIS IS THE O(log n) THE PROBLEM ASKS FOR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
41 / VIDEO UNIT 06 · Single Element by Parity

BS-8. Single Element in Sorted Array

STRIVER A2Z
Single Element by Parity
RUNTIME 22:16
AFTER THIS → 3 DRILLS · PROBLEM #07
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
42 / DRILL UNIT 06 · Single Element by Parity · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why align mid to an even index before comparing a[mid] with a[mid+1]?

Alignment makes the parity test meaningful. The invariant is “a[even] == a[even+1]” for every pair before the single element. If mid is odd, comparing a[mid] with a[mid+1] straddles two different pairs and tells you nothing. Nudging mid down to even (mid--) guarantees you are looking at a pair's first element, so the equality check directly answers “is the pairing still normal up to here?” — which is the compass for which half to keep.

DRILL 02 · TRACE

On [1, 1, 2, 2, 3, 4, 4, 8, 8], the first mid aligns to index 4 (value 3). Is the pair intact, and where does the search go?

The pair is broken, so the single is here or left. Aligned mid = 4 holds 3, and a[5] = 4 ≠ 3, so the normal even-first pairing has already been disrupted at or before index 4 — the lone element cannot be to the right. Set hi = mid = 4 and continue. Two steps later the interval collapses onto index 4, the single element 3. Note hi = mid, not mid − 1: index 4 itself is a live candidate.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
43 / DRILL UNIT 06 · Single Element by Parity · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

A simpler solution XORs every element together and returns the result. Why might an interviewer reject it here?

XOR is correct but too slow for the stated bound. XOR-ing the whole array cancels every pair and leaves the loner in O(n) time and O(1) space — genuinely elegant, and the right answer to Single Number on an unsorted array. But this problem hands you a sorted array and asks for O(log n), which is a signal that it wants you to exploit the order. Recognising that the sortedness plus a log-n requirement rules out the linear XOR trick is the actual test being set.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
44 / MECHANISM UNIT 06 · SINGLE · CODE MIRRORED

PAIR PARITY IS THE COMPASS

Every element is doubled except one. Before the single, each pair sits at an even-then-odd index; after it, the parity flips to odd-then-even. So align mid to its pair's even index and check whether a[mid] == a[mid+1]: intact means the break is to the right, broken means it is here or left. O(log n), and no XOR scan of the whole array.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
45 / PROBLEM #07 · PARITY · MED

Single Element in a Sorted Array

MED parity ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Every element appears exactly twice except one”, the array is sorted, and the required complexity is O(log n). That log requirement on a sorted array is the signal: the O(n) XOR trick is not what is being asked for — you are meant to binary-search on the structure the pairing creates.

INTUITION

Before the single element, pairs align even-then-odd; after it, the alignment flips. So at any mid, snap it down to the even index of its pair and check whether a[mid] == a[mid+1]. If they match, the pairing is still normal up to here, so the lone element is to the right (lo = mid + 2). If they do not, the shift has already happened, so the lone element is at mid or to its left (hi = mid). The interval collapses onto the single element.

STEPS
  1. Set lo = 0, hi = n − 1; loop while lo < hi
  2. mid = lo + (hi − lo) / 2; if mid is odd, decrement it to the pair's even index
  3. If a[mid] == a[mid + 1], the pairing is intact: lo = mid + 2
  4. Otherwise the pairing is broken here or earlier: hi = mid
  5. When lo == hi, a[lo] is the single element
  6. O(log n), unlike the O(n) whole-array XOR
BRUTEO(n) — XOR everything
OPTIMALO(log n)
↕ SCROLL
// Pairs are even-first before the single element, odd-first after it.
// Align mid to the pair's even index, then a[mid]==a[mid+1] tells you which
// side the parity break -- the lone element -- is on. O(log n), no XOR scan.
int singleNonDuplicate(vector<int>& a) {
    int lo = 0, hi = a.size() - 1;

    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (mid % 2 == 1) mid--;                 // align to the even index
        if (a[mid] == a[mid + 1]) lo = mid + 2;  // pairing intact: go right
        else hi = mid;                           // pairing broken: here or left
    }
    return a[lo];
}
TIMEO(log n)binary search on the parity of the pair index
SPACEO(1)two indices
TRAP

Forgetting to align mid to an even index. Without the if (mid % 2 == 1) mid-- nudge, comparing a[mid] with a[mid+1] can straddle two different pairs, and the parity test becomes meaningless — the search wanders and returns a random element. The other temptation is to XOR the whole array: it is correct and elegant but O(n), and the problem's explicit O(log n) requirement exists specifically to rule it out. Reading that requirement as a constraint on your approach, not just your final answer, is the point.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #07 · SINGLE ELEMENT IN A SORTED ARRAY

BS-8. Single Element in Sorted Array

The walkthrough for #07 Single Element in a Sorted Array. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-8. Single Element in Sorted Array
RUNTIME 22:16
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
46 / INTRO UNIT 07 · Find a Peak

UNIT 07 — Find a Peak

The most surprising result in the deck: binary search works on an array that is not sorted at all. A peak is any element greater than both neighbours (the ends count as −∞). At any mid, comparing a[mid] with a[mid+1] reveals a slope — and because the boundaries fall off to negative infinity, walking uphill from anywhere must eventually hit a peak. So discard the downhill side and the interval collapses onto one.

THE QUESTION THIS LECTURE ANSWERS

HOW CAN BINARY SEARCH FIND A PEAK WHEN THE ARRAY IS NOT EVEN SORTED?

PEAKSLOPEUPHILLNOT SORTEDDECIDABLE DIRECTION
WHAT TO WATCH FOR
  • 01THE ARRAY IS NOT SORTED — BINARY SEARCH NEEDS ONLY A DECIDABLE DIRECTION, NOT ORDER
  • 02a[mid] < a[mid+1] ⇒ UPHILL IS RIGHT, A PEAK LIES THERE: lo = mid+1
  • 03a[mid] > a[mid+1] ⇒ mid ITSELF COULD BE A PEAK: hi = mid (KEEP IT)
  • 04THE ENDS ACT AS -INFINITY, WHICH GUARANTEES A PEAK ALWAYS EXISTS TO WALK TOWARD
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
47 / VIDEO UNIT 07 · Find a Peak

BS-9. Find Peak Element

STRIVER A2Z
Find a Peak
RUNTIME 32:53
AFTER THIS → 3 DRILLS · PROBLEM #08
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
48 / DRILL UNIT 07 · Find a Peak · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Binary search usually needs a sorted array. What does the peak problem show it actually requires?

Binary search needs a decidable direction, not sortedness. The real requirement is that, from the middle, you can always prove which half contains a valid answer. A sorted array gives that via value comparison; a peaked array gives it via slope — a[mid] < a[mid+1] proves a peak lies to the right, because you can keep climbing and the boundary is −∞. Internalising this is what lets you spot binary search in problems that never mention sorting — including most of deck 2, where the “array” is a range of candidate answers.

DRILL 02 · TRACE

On [1, 3, 5, 4, 2, 6, 7, 0], the first mid is index 3 (value 4), and a[4] = 2. Which way does the search go, and why is that safe?

Left, keeping mid. Since a[3] = 4 > a[4] = 2, the slope falls to the right, which means climbing leftward leads uphill and a peak must exist at mid or to its left (the left boundary is −∞). So hi = mid = 3. It is safe even though 6 and 7 to the right are larger, because the problem asks for any peak, not the global maximum — and index 2 (value 5, a local peak) does lie in the surviving left interval. The MECHANISM slide converges there.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
49 / DRILL UNIT 07 · Find a Peak · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This peak-finder compares a[mid] with a[mid-1] instead of a[mid+1], and reads out of bounds. What is the cleaner fix the standard version uses?

while (lo < hi) {
    int mid = lo + (hi - lo) / 2;
    if (a[mid] > a[mid - 1]) lo = mid;
    else hi = mid - 1;
}

Compare forward, with the half-open shrink. The clean invariant is lo < hi with hi = mid, which guarantees mid is never the last index, so a[mid+1] is always safe to read — no boundary guard required. Comparing backward to a[mid-1] reads out of bounds at mid = 0 and pairs awkwardly with the shrink direction. The forward comparison plus hi = mid / lo = mid + 1 is why the standard peak search has no edge cases at all.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
50 / MECHANISM UNIT 07 · PEAK · CODE MIRRORED

FOLLOW THE SLOPE — UPHILL ALWAYS ENDS AT A PEAK

No sortedness at all, and it still works. a[mid] vs a[mid+1] tells you which way is uphill, and since the ends behave as −∞, walking uphill from anywhere must terminate at a peak. Rising to the right ⇒ a peak lies right; falling ⇒ mid itself could be one. The realisation that binary search does not require a sorted array, only a decidable direction, is the payoff.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
51 / PROBLEM #08 · PEAK · MED

Find Peak Element

MED peak ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Find a peak / local maximum” and — crucially — the array is not stated to be sorted, yet O(log n) is expected. That combination is the tell that binary search can run on a slope rather than on order: you do not need sortedness, only a rule for which direction leads to an answer.

INTUITION

A peak is an element greater than both neighbours, with the ends treated as −∞ so a peak always exists. At any mid, compare with the next element: if a[mid] < a[mid+1] the slope rises to the right, so a peak lies that way — discard mid and the left. Otherwise the slope falls, so mid itself could be a peak or one lies left — set hi = mid. Following uphill from anywhere must reach a peak, so the interval safely collapses onto one.

STEPS
  1. Set lo = 0, hi = n − 1; loop while lo < hi (half-open, so mid + 1 is always valid)
  2. mid = lo + (hi − lo) / 2
  3. If a[mid] < a[mid + 1], uphill is right: lo = mid + 1
  4. Otherwise mid could be a peak or one lies left: hi = mid
  5. When lo == hi, index lo is a peak
  6. Works with no sortedness — only a decidable uphill direction
BRUTEO(n)
OPTIMALO(log n)
↕ SCROLL
// Not sorted -- and binary search still works, because a[mid] vs a[mid+1]
// gives a decidable uphill direction, and the ends act as -infinity so a
// peak always exists to climb toward.
int findPeakElement(vector<int>& a) {
    int lo = 0, hi = a.size() - 1;

    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] < a[mid + 1]) lo = mid + 1;   // uphill is to the right
        else hi = mid;                           // mid could be the peak
    }
    return lo;
}
TIMEO(log n)each comparison discards a downhill half
SPACEO(1)two indices
TRAP

Reading a[mid − 1] or a[mid + 1] without guarding the boundary. The clean fix is the half-open loop lo < hi with hi = mid, which guarantees mid is never the last index, so a[mid + 1] is always in range — no special cases. The conceptual trap is assuming you need the global maximum: the problem asks for any peak, which is what makes the O(log n) slope-following valid; hunting the global max would force O(n).

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #08 · FIND PEAK ELEMENT

BS-9. Find Peak Element

The walkthrough for #08 Find Peak Element. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-9. Find Peak Element
RUNTIME 32:53
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
52 / INTRO UNIT 08 · Flatten the Grid

UNIT 08 — Flatten the Grid

When a matrix has every row sorted and the first element of each row larger than the last of the previous row, reading it top-to-bottom, left-to-right yields one long sorted sequence. So it is not really 2D at all — binary search the flat index range [0, m·n − 1] and convert each midpoint back with row = mid / cols, col = mid % cols. Ordinary binary search, plus one line of index arithmetic.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU SEARCH A FULLY SORTED MATRIX WITHOUT WRITING ANY 2D LOGIC?

FLATTENVIRTUAL INDEXrow = k/colscol = k%colsO(log mn)
WHAT TO WATCH FOR
  • 01THE FLATTENING WORKS ONLY BECAUSE EACH ROW'S FIRST > THE PREVIOUS ROW'S LAST
  • 02mid IS A FLAT INDEX IN [0, m·n−1]; row = mid/cols, col = mid%cols RECOVERS THE CELL
  • 03IT IS LITERALLY PLAIN BINARY SEARCH — NOTHING ABOUT THE LOOP CHANGES
  • 04MATRIX II IS DIFFERENT: ITS ROWS ARE NOT GLOBALLY ORDERED, SO THIS TRICK FAILS THERE
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
53 / VIDEO UNIT 08 · Flatten the Grid

BS-24. Search in a 2D Matrix - I

STRIVER A2Z
Flatten the Grid
RUNTIME 15:42
AFTER THIS → 3 DRILLS · PROBLEM #09
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
54 / DRILL UNIT 08 · Flatten the Grid · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why can Search a 2D Matrix (I) be solved as a single 1D binary search, while Matrix II cannot?

Global ordering across the row seams. The flatten trick treats the matrix as row0 ++ row1 ++ …, and that concatenation is sorted only if each row starts higher than the previous row ended — which Matrix I guarantees and Matrix II does not. In Matrix II a row can start lower than a cell in the row above, so the flattened sequence has descents and binary search on it is invalid. Recognising which of the two ordering guarantees you have is the whole choice between the two problems.

DRILL 02 · TRACE

In a 3×4 matrix, binary search picks flat index mid = 6. Which cell (row, col) is that?

(1, 2). With 4 columns, row = 6 / 4 = 1 and col = 6 % 4 = 2. The division counts how many full rows fit below index 6, and the remainder is the offset within that row. This single line is the entire adaptation — everything else is the plain binary search from unit 1. The MECHANISM slide shows the flat midpoint lighting the corresponding grid cell each step.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
55 / DRILL UNIT 08 · Flatten the Grid · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

A variant gives you the matrix as a true 2D structure but asks you not to allocate a flattened copy. Does the trick still apply?

Yes — the flattening is virtual. You never construct a 1D array; you binary-search the index range [0, m·n − 1] and, each time you need the value at mid, compute m[mid / cols][mid % cols] on the fly. That is O(1) extra space and O(log mn) time. The insight worth keeping is that “flatten” is a way of thinking about the index space, not an instruction to copy memory.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
56 / MECHANISM UNIT 08 · GRID2D · CODE MIRRORED

A SORTED MATRIX IS A 1D ARRAY IN DISGUISE

When a matrix is fully sorted — each row ascending and every row's first element larger than the previous row's last — reading it left-to-right, top-to-bottom gives one long sorted sequence. So do not think in 2D at all: binary search the flat index range [0, m·n−1], and convert each midpoint back with row = mid / cols, col = mid % cols. Ordinary binary search, one line of index arithmetic.

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
57 / PROBLEM #09 · GRID · MED

Search a 2D Matrix

MED grid ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A matrix that is fully sorted — each row ascending, and the first entry of every row greater than the last of the row above — with a target to locate in O(log mn). That global ordering across the row seams is the specific signal for the flatten trick; it is what separates this from Matrix II.

INTUITION

Because the rows chain together into one sorted sequence, treat the matrix as a virtual 1D array of length m·n and run plain binary search on the index range [0, m·n − 1]. Whenever you need the value at a midpoint, recover its cell with row = mid / cols and col = mid % cols. No copy is made; the flattening is purely a way of indexing.

STEPS
  1. Set lo = 0, hi = m·n − 1 over the virtual flat array
  2. mid = lo + (hi − lo) / 2
  3. Recover the cell: row = mid / cols, col = mid % cols
  4. Compare m[row][col] to the target and discard a half exactly as in 1D
  5. Return true on a match, false when the interval empties
  6. O(log mn) time, O(1) space — no flattened copy is allocated
BRUTEO(m·n)
OPTIMALO(log mn)
↕ SCROLL
// Fully sorted matrix = one sorted array of length m*n. Binary search
// the flat index; recover the cell with row = mid/cols, col = mid%cols.
bool searchMatrix(vector<vector<int>>& m, int t) {
    int R = m.size(), C = m[0].size();
    int lo = 0, hi = R * C - 1;

    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        int v = m[mid / C][mid % C];        // flatten the index
        if (v == t) return true;
        if (v < t) lo = mid + 1;
        else       hi = mid - 1;
    }
    return false;
}
TIMEO(log mn)one binary search over m·n virtual indices
SPACEO(1)index arithmetic only; no flattened array
TRAP

Applying the flatten to Matrix II. The whole trick rests on each row starting higher than the previous row ended; Matrix II makes no such promise, so its concatenated rows are not globally sorted and binary search on the flat index silently returns wrong answers. The other slip is the index math — mixing up mid / cols and mid % cols, or using rows instead of columns as the divisor. It is cols that divides, because rows are laid end to end.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #09 · SEARCH A 2D MATRIX

BS-24. Search in a 2D Matrix - I

The walkthrough for #09 Search a 2D Matrix. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-24. Search in a 2D Matrix - I
RUNTIME 15:42
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
58 / INTRO UNIT 09 · The Staircase Walk

UNIT 09 — The Staircase Walk

Matrix II is only row- and column-sorted, so it cannot be flattened. But start at the top-right corner and something clean happens: that cell is the largest in its row and the smallest in its column. So one comparison always eliminates an entire line — too big means the whole column below is bigger (drop the column); too small means the whole row to the left is smaller (drop the row). Each step removes a row or a column, giving O(m + n).

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU SEARCH A ROW- AND COLUMN-SORTED MATRIX THAT WON'T FLATTEN?

STAIRCASETOP-RIGHT CORNERMONOTONE PIVOTELIMINATE A LINEO(m+n)
WHAT TO WATCH FOR
  • 01THE TOP-RIGHT (OR BOTTOM-LEFT) CORNER IS THE MONOTONE PIVOT — A CORNER MATTERS
  • 02TOO BIG ⇒ DROP THE COLUMN (c--); TOO SMALL ⇒ DROP THE ROW (r++)
  • 03EACH STEP ELIMINATES A WHOLE LINE, SO IT IS O(m+n), NOT O(log mn)
  • 04STARTING AT TOP-LEFT OR BOTTOM-RIGHT DOES NOT WORK — THOSE CORNERS ARE AMBIGUOUS
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
59 / VIDEO UNIT 09 · The Staircase Walk

BS-25. Search in a 2D Matrix - II

STRIVER A2Z
The Staircase Walk
RUNTIME 15:29
AFTER THIS → 3 DRILLS · PROBLEM #10
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
60 / DRILL UNIT 09 · The Staircase Walk · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why start at the top-right corner rather than the top-left?

The top-right corner is a monotone pivot. Being the largest in its row and smallest in its column means: if it is bigger than the target, every cell below it (same column) is also bigger, so the column is useless — drop it. If it is smaller, every cell to its left (same row) is also smaller — drop the row. The top-left corner is the minimum of both its row and column, so “too small” could send you right OR down with no way to choose. Bottom-left works too (min of row, max of column); the two 'mixed' corners are the valid starts.

DRILL 02 · TRACE

Searching for 5 in [[1,4,7,11],[2,5,8,12],[3,6,9,16],[10,13,14,17]] from the top-right, what are the first three cells visited?

(0,3)=11 → (0,2)=7 → (0,1)=4. Start at 11 (top-right): 11 > 5, drop the column, move left to 7; 7 > 5, drop the column, move to 4; 4 < 5, now drop the row and move down to (1,1)=5 — found. The path is a staircase hugging the boundary between values below and above the target, which is exactly why it is O(m + n): it can traverse at most m rows down and n columns left. The MECHANISM slide traces this path and greys the eliminated rows and columns.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
61 / DRILL UNIT 09 · The Staircase Walk · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This staircase search starts at the bottom-right corner. Why does it fail?

int r = m.size() - 1, c = m[0].size() - 1;   // bottom-right
while (r >= 0 && c >= 0) {
    if (m[r][c] > t) c--; else r++;
}

Bottom-right is the global maximum of its row and column, so it is not a pivot. When m[r][c] > t, both the cell above and the cell to the left could contain the target, and you cannot rule out a whole line — the elimination that makes the algorithm work is impossible. The valid starting corners are the two “mixed” ones: top-right (max row / min col) or bottom-left (min row / max col). The corner choice is not cosmetic; it is what creates the decisive comparison.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
62 / MECHANISM UNIT 09 · STAIRCASE · CODE MIRRORED

WALK FROM A CORNER — EACH STEP KILLS A ROW OR A COLUMN

Matrix II is only row- and column-sorted, so the flatten trick fails. Instead start at the top-right corner: that cell is the largest in its row and the smallest in its column, which makes every comparison decisive. Too big? Everything below it in the column is bigger too — drop the column. Too small? Everything left in the row is smaller — drop the row. Each step removes a whole line, so it is O(m + n).

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
63 / PROBLEM #10 · GRID · MED

Search a 2D Matrix II

MED grid ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A matrix sorted along rows and down columns, but not globally — so no flatten. The staircase signal is exactly this weaker ordering plus a target to find; the expected complexity O(m + n) is the tell that you eliminate a whole line per step rather than halving.

INTUITION

Start at the top-right corner, which is the maximum of its row and the minimum of its column — a monotone pivot. Compare it to the target: if it is too big, every cell below it in the column is also too big, so drop the column (move left); if it is too small, every cell to its left in the row is smaller, so drop the row (move down). Each step removes an entire row or column, so the walk is O(m + n).

STEPS
  1. Start at r = 0, c = cols − 1 (top-right corner)
  2. While r is in range and c ≥ 0, inspect m[r][c]
  3. If it equals the target, return true
  4. If it is greater than the target, drop the column: c −= 1
  5. If it is less, drop the row: r += 1
  6. Falling off the grid means the target is absent — O(m + n) total
BRUTEO(m·n)
OPTIMALO(m + n)
↕ SCROLL
// Row- and column-sorted, but not globally -- so flattening fails.
// Start TOP-RIGHT: max of its row, min of its column, so each comparison
// eliminates a whole line. O(m + n).
bool searchMatrix(vector<vector<int>>& m, int t) {
    int r = 0, c = m[0].size() - 1;         // top-right corner

    while (r < (int)m.size() && c >= 0) {
        if (m[r][c] == t) return true;
        if (m[r][c] > t) c--;               // too big: drop the column
        else             r++;               // too small: drop the row
    }
    return false;
}
TIMEO(m + n)each step removes a whole row or column
SPACEO(1)two indices
TRAP

Starting at the wrong corner. Top-left and bottom-right are the minimum and maximum of both their row and column, so a comparison there cannot single out a line to eliminate — the algorithm stalls or wanders. Only the two mixed corners work: top-right or bottom-left. The second trap is reaching for the flatten trick out of habit; it needs global ordering that this matrix does not have, and it fails on the very first row seam where a lower row starts below the row above it.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #10 · SEARCH A 2D MATRIX II

BS-25. Search in a 2D Matrix - II

The walkthrough for #10 Search a 2D Matrix II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-25. Search in a 2D Matrix - II
RUNTIME 15:29
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
64 / INTRO UNIT 10 · A Peak in Two Dimensions

UNIT 10 — A Peak in Two Dimensions

A 2D peak is a cell greater than all four of its neighbours, and — exactly as in 1D — you do not need the matrix sorted, only a climbable direction. Binary search the columns: in the middle column, find the row holding its maximum. If that cell also beats its left and right neighbours it is a peak; if not, the larger horizontal neighbour points to a half of columns that must contain one. Taking the column's maximum is what guarantees you never need to search up or down.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND A 2D PEAK IN O(m log n) WITHOUT SCANNING EVERY CELL?

2D PEAKSEARCH ON COLUMNSCOLUMN MAXIMUMSLOPE IN 2DO(m log n)
WHAT TO WATCH FOR
  • 01BINARY SEARCH RUNS ON COLUMNS; WITHIN A COLUMN YOU TAKE ITS MAXIMUM ROW
  • 02THE COLUMN MAX BEATS ITS VERTICAL NEIGHBOURS BY DEFINITION — SO ONLY LEFT/RIGHT MATTER
  • 03IF A NEIGHBOUR IS LARGER, A PEAK LIES ON THAT SIDE — THE SAME SLOPE ARGUMENT AS 1D
  • 04O(m log n): log n COLUMNS, EACH COSTING AN O(m) SCAN FOR ITS MAXIMUM
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
65 / VIDEO UNIT 10 · A Peak in Two Dimensions

BS-26. Find Peak Element-II

STRIVER A2Z
A Peak in Two Dimensions
RUNTIME 20:02
AFTER THIS → 3 DRILLS · PROBLEM #11
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
66 / DRILL UNIT 10 · A Peak in Two Dimensions · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Within the chosen middle column, why take the row with the column's MAXIMUM rather than any cell?

Taking the column maximum collapses the 2D problem to 1D. A peak must exceed all four neighbours; by picking the largest cell in the column you have already guaranteed it beats the cells above and below it, so the only remaining question is the horizontal one — is a left or right neighbour bigger? That is precisely the 1D peak decision, and following the larger side must reach a peak because the column-max envelope keeps rising. Without taking the max, a vertical neighbour could exceed your cell and the slope argument would break.

DRILL 02 · TRACE

In [[10,20,15],[21,30,14],[7,16,32]], the search starts at the middle column (index 1). What is the column's max, and is it a peak?

30 at (1,1) is a peak. Column 1 holds 20, 30, 16, so its maximum is 30 at row 1. Its horizontal neighbours are m[1][0] = 21 and m[1][2] = 14, both smaller — and because 30 is the column max it already beats m[0][1] = 20 and m[2][1] = 16 above and below. All four neighbours are smaller, so 30 is a valid 2D peak and the search returns immediately. One column examined, no descent into rows above or below.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
67 / DRILL UNIT 10 · A Peak in Two Dimensions · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Why is the complexity O(m log n) and not O(log m · log n)?

Finding a column's maximum is an O(m) linear scan, not a binary search. The columns are not sorted, so there is no way to locate the maximum faster than looking at all m entries. Binary search still applies to which column — that gives the log n factor — but each of those log n probes pays O(m) for its column scan, hence O(m log n). Recognising exactly which dimension supports binary search and which forces a scan is what pins down the complexity.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
68 / MECHANISM UNIT 10 · PEAK2D · CODE MIRRORED

BINARY SEARCH THE COLUMNS, TAKE EACH COLUMN'S MAX

A 2D peak beats all four neighbours, and — as in 1D — you do not need sortedness, only a climbable direction. Binary search the columns: in the middle column find the row holding its maximum. If that cell beats its left and right neighbours it is a peak; otherwise the larger neighbour points to a half that must contain one, because the column max guarantees you never need to look up or down. O(m log n).

ONE COLLAPSE, EVERY ALGORITHM
CODE MIRROR
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
69 / PROBLEM #11 · PEAK · MED

Find a Peak Element II

MED peak ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Find any peak in a matrix” — a cell strictly greater than its four neighbours — with a better-than-O(mn) bound expected. As in the 1D peak problem, the absence of any sortedness requirement plus a sub-linear target is the signal that binary search runs on a slope, here across columns.

INTUITION

Binary search the columns. In the middle column, scan for the row holding its maximum; that cell already beats its vertical neighbours, so only its horizontal neighbours matter. If it beats both, it is a peak. Otherwise the larger horizontal neighbour lies in a half of columns that must contain a peak — because following the rising column-maxima cannot run off the edge without cresting. Discard the other half and repeat.

STEPS
  1. Set lo = 0, hi = cols − 1 over the columns
  2. mid = lo + (hi − lo) / 2; scan column mid for the row r of its maximum
  3. Compare m[r][mid] to its left and right neighbours
  4. If it beats both, return (r, mid) — a 2D peak
  5. If the right neighbour is larger, a peak lies right: lo = mid + 1
  6. Otherwise a peak lies left: hi = mid − 1. Total O(m log n)
BRUTEO(m·n)
OPTIMALO(m log n)
↕ SCROLL
// A 2D peak beats all four neighbours. Binary search the COLUMNS; in each,
// take the row with the column's max -- that already beats up/down, so only
// left/right decide. Follow the larger neighbour. O(m log n).
vector<int> findPeakGrid(vector<vector<int>>& m) {
    int lo = 0, hi = m[0].size() - 1;

    while (lo <= hi) {
        int mc = lo + (hi - lo) / 2;
        int mr = 0;                                    // row of the column max
        for (int i = 1; i < (int)m.size(); i++)
            if (m[i][mc] > m[mr][mc]) mr = i;
        int L = mc ? m[mr][mc - 1] : -1;
        int Rn = mc + 1 < (int)m[0].size() ? m[mr][mc + 1] : -1;
        if (m[mr][mc] > L && m[mr][mc] > Rn) return {mr, mc};
        else if (Rn > m[mr][mc]) lo = mc + 1;          // climb right
        else hi = mc - 1;                              // climb left
    }
    return {-1, -1};
}
TIMEO(m log n)log n column probes, each an O(m) scan for the column maximum
SPACEO(1)a few indices
TRAP

Comparing an arbitrary cell instead of the column's maximum. The slope argument only holds for the largest cell in the column — that is what guarantees the vertical neighbours are already beaten, so a larger horizontal neighbour reliably points toward a peak. Pick any other cell and a vertical neighbour might exceed it, breaking the invariant and sending the search to a column with no peak. The second trap is expecting O(log m · log n): the column is unsorted, so locating its maximum is an unavoidable O(m) scan, making the true cost O(m log n).

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
SOLUTION #11 · FIND A PEAK ELEMENT II

BS-26. Find Peak Element-II

The walkthrough for #11 Find a Peak Element II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
BS-26. Find Peak Element-II
RUNTIME 20:02
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
70 / RECALL RETRIEVAL, NOT RECOGNITION

PICK THE PRIMITIVE FROM THE STATEMENT

DRILL 01 · TRANSFER

“A sorted array was rotated an unknown number of times; find the value at the k-th position of the original.” What is the key sub-problem?

Find the minimum, which is the rotation offset. The index of the minimum is exactly how many places the array was rotated, and it is found in O(log n) by comparing a[mid] to a[hi]. Once you know the offset, the original index k maps to (offset + k) % n in the rotated array. This is why find-min is a load-bearing primitive, not a one-off: the rotation count it returns unlocks a family of rotated-array questions.

DRILL 02 · RECALL

You are told an array is sorted and asked for the number of elements strictly less than a target. Which primitive, in one call?

lower_bound(target). It returns the first index with a[i] ≥ target — and because everything before that index is strictly less than the target, that index is the count. No scan, no subtraction. Recognising that lower_bound's return value doubles as a count (and upper_bound − lower_bound is the number of occurrences) is what turns it from a search into a Swiss-army primitive.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
71 / TRAPS PLAUSIBLE, WRONG, AND SILENT

SIX WAYS TO BE PLAUSIBLY WRONG

Binary search is short, which is exactly why its bugs are subtle — an overflow, a non-shrinking interval, a boundary off by one. Every one of these compiles and returns a plausible answer.

(lo + hi) OVERFLOW

(lo + hi) / 2 wraps negative when both indices are large — routine in binary-search-on-answers, where hi can be 10⁹ or more. Always lo + (hi − lo) / 2. Silent: it produces a bad index, not a compile error.

lo = mid PAIRED WITH hi = mid

If one branch keeps mid in the interval, the other must move past it (mid ± 1). Two branches that both keep mid can leave the interval the same size forever — an infinite loop on adjacent lo, hi.

STOPPING AT THE FIRST MATCH FOR FIRST/LAST

Plain search returns an occurrence, not the first. For boundary problems you must record the hit and keep narrowing. Returning early passes the single-occurrence tests and fails on duplicates.

hi = mid − 1 WHEN mid MIGHT BE THE ANSWER

In find-min and peak, mid itself can be the answer, so the shrink is hi = mid, not mid − 1. Using mid − 1 can step over the very element you want — a plausible wrong answer on rotated or peaked input.

ASSUMING ROTATED ARRAYS HAVE NO DUPLICATES

With duplicates, a[lo] == a[mid] == a[hi] makes it impossible to tell which half is sorted. You must shrink both ends by one, which drops the guarantee to O(n) worst case. Search II is exactly this case, and forgetting it gives wrong answers on [3,1,3,3,3].

USING THE WRONG BOUND ON A 2D MATRIX

Search-a-2D-Matrix I is fully sorted when flattened (treat it as one array of length m·n); Matrix II is only row- and column-sorted (walk the staircase from a corner). Applying the flatten trick to II gives wrong answers — the row boundaries do not line up.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
72 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

Eleven searches, one collapse. The right-hand column is the recognition — the question asked at mid — which is the part that is actually hard.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
Plain binary search
O(log n)
O(1)
sorted array, find an exact target
lower_bound / upper_bound
O(log n)
O(1)
first index ≥ / > target — insert position, counts
First & last occurrence
O(log n)
O(1)
range of a value — two boundary searches
Rotated search
O(log n)
O(1)
sorted then rotated — find the sorted half first
Rotated with duplicates
O(n) worst
O(1)
a[lo]==a[mid]==a[hi] — shrink both ends
Find minimum / rotation count
O(log n)
O(1)
compare a[mid] to a[hi], no target
Single element by parity
O(log n)
O(1)
all doubled but one — search the parity break
Find a peak
O(log n)
O(1)
unsorted OK — follow a[mid] vs a[mid+1] uphill
2D matrix, fully sorted
O(log mn)
O(1)
flatten indices: row = k/cols, col = k%cols
2D matrix, row/col sorted
O(m+n)
O(1)
start top-right, move down or left each step
Peak in 2D
O(m log n)
O(1)
binary search columns, max of each column
INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
73 / CLOSE STEP 04 · DECK 1 OF 2

ELEVEN, ONE COLLAPSE

Once the interval-collapse invariant is automatic, the answer-space reframe in deck 2 stops looking like a different technique and starts looking like the same one pointed at a range of numbers instead of an array.

00%
OF THIS DECK SOLVED
← ALL TOPICSDECK 2 · ON THE ANSWER →STEP 03 · ARRAYS

Lectures are Striver's A2Z DSA course. Problem links are LeetCode. 10 units from the 29-lecture playlist; the answer-space problems are deck 2.

INVARIANT · BINARY SEARCH · COLLAPSE THE INTERVAL · DECK 1 OF 2
■ TOO SMALL TO READ
1280×720
INVARIANT · STEP 04 · DECK 1 OF 2

This one needs a laptop

Not a preference — an arithmetic one. Every slide is a fixed 1280 × 720 stage: a graph animating beside the code that drives it, with the problems laid out two columns wide. It scales as a single piece, so on a screen this size the body text comes out around 4px tall.

Shrinking it further would not help, and rebuilding it to reflow would mean losing the thing that makes it worth reading.

YOUR SCREEN0 × 0
NEEDED1060 × 610
WHAT IS WAITING ON THE LAPTOP
WATCH IT RUN, THEN RUN IT FROM MEMORY
Your progress is saved per device, so anything you tick on the laptop will be waiting there.