INVARIANT · ARRAYS · ONE PASS · TWO INDICES
01
00/18
01 / COVER STEP 03 · ARRAYS
INVARIANT · STEP 03 · DECK 1 OF 2
18 PROBLEMS 9 IDEAS

The first 18 rows look like 18 unrelated tricks. They are not. Strip the titles away and 9 ideas cover all of them — a write pointer, a running best, a cancelling XOR, a hashmap holding the complement. Learn the 9; the 18 follow.

18Problems
9Patterns
14Units
15Lectures
← → ↑ ↓  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 · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

This is not a list of problems. It is 14 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.

18 PROBLEMS · EVERY ONE LINKS TO LEETCODE TO SOLVE

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 18 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.

ARRAYS [EASY] · 06
ARRAYS [MEDIUM] · 12
SOLVED HAS A LEETCODE LINK
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

WHAT THE STATEMENT IS TELLING YOU

Nobody is asked to “use a write pointer” in an interview. You are handed a paragraph and expected to hear which pattern it permits — and the phrases that give it away are astonishingly consistent.

“IN PLACE”, “O(1) EXTRA SPACE”, OR A RETURNED LENGTH

the answer is shorter than the input, or is the input rearranged

TWO INDICES — r READS, w COMMITSO(n) time · O(1) space
“EVERY ELEMENT APPEARS TWICE EXCEPT…” · A MISSING VALUE FROM A KNOWN RANGE

the noise is paired and the signal is not

XOR EVERYTHING, OR USE THE n(n+1)/2 IDENTITYO(n) time · O(1) space
“A PAIR / TRIPLET / QUADRUPLE SUMMING TO A TARGET”

order does not matter and duplicates must be skipped

SORT, FIX k−2 INDICES, CLOSE WITH TWO POINTERSO(nk−1) time · O(1) space
“CONTIGUOUS SUBARRAY” + MAXIMUM OR A COUNT

the answer is a run, not a subset

KADANE IF MAXIMISING · PREFIX SUM + HASHMAP IF COUNTINGO(n) time · O(1) or O(n) space
“MORE THAN n/2” OR “MORE THAN n/3 TIMES”

a threshold that guarantees at most k−1 answers exist

BOYER–MOORE VOTING, THEN A VERIFY PASSO(n) time · O(1) space
A MATRIX, AND THE WORD “IN PLACE” OR “SPIRAL”

nothing is searched — the answer is a formula from index to index

TRANSPOSE + REVERSE, OR FOUR SHRINKING BOUNDSO(n²) time · O(1) space
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
05 / CONSTRAINTS BEFORE YOU WRITE A LINE

READ THE ANSWER OFF THE CONSTRAINTS

Before writing anything, read the input bound and it tells you the complexity you are allowed. Type a value for n and the row that survives lights up.

n ≤
BUDGET
WHAT THAT BUYS YOU
10–12
O(n!)
permutations, brute-force orderings
20–25
O(2ⁿ) · O(2ⁿ·n)
subsets, bitmask DP, meet-in-the-middle
100
O(n³)
the O(n³) 3Sum you must NOT write · Floyd–Warshall
10³
O(n²)
nested loops, the pair-checking brute force
10⁵
O(n log n)
sort-then-scan · 3Sum/4Sum · merge-counting · intervals
10⁶–10⁸
O(n) · O(log n)
one pass: write pointer, Kadane, voting, XOR, prefix sums

THE GOLD-EDGED ROWS ARE WHERE THIS TOPIC LIVES · PAIR-CHECKING DIES AT 10⁴ · EVERY OPTIMAL SOLUTION HERE IS ONE PASS OR ONE SORT

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
06 / ROADMAP WHAT YOU ARE ABOUT TO DO

THE WHOLE RUN, 14 UNITS

All 14 units, covering every Easy and Medium row on the sheet. The eight Hard rows are deck 2 — compositions of exactly these nine patterns, worth nothing until they are automatic.

UNIT 01

The Write Pointer

▶ 43:264 DRILLS1 PROBLEM
UNIT 02

Rotation & Compaction

▶ 73:174 DRILLS2 PROBLEMS
UNIT 03

XOR & The Sum Identity

▶ 38:003 DRILLS3 PROBLEMS
UNIT 04

The Hashmap Complement

▶ 18:203 DRILLS1 PROBLEM
UNIT 05

Dutch National Flag

▶ 25:073 DRILLS1 PROBLEM
UNIT 06

Boyer-Moore Voting

▶ 18:133 DRILLS1 PROBLEM
UNIT 07

Kadane's Algorithm

▶ 20:094 DRILLS2 PROBLEMS
UNIT 08

Rearrange by Sign

▶ 21:373 DRILLS1 PROBLEM
UNIT 09

Next Permutation

▶ 28:153 DRILLS1 PROBLEM
UNIT 10

Longest Consecutive Run

▶ 23:113 DRILLS1 PROBLEM
UNIT 11

The Matrix as Its Own Flag

▶ 30:073 DRILLS1 PROBLEM
UNIT 12

Transpose & Reverse

▶ 17:473 DRILLS1 PROBLEM
UNIT 13

Four Shrinking Bounds

▶ 16:333 DRILLS1 PROBLEM
UNIT 14

Prefix Sums + Hashmap

▶ 24:093 DRILLS1 PROBLEM
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
07 / WARMUP LOAD THE TOPIC BEFORE UNIT 01 · 1 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · RECALL

An array is scanned by two indices, r and w, with w starting at 0 and only advancing when a value is kept. Can w ever overtake r?

Never. Each iteration advances r exactly once and w at most once, so w ≤ r is an invariant of the loop. That single inequality is the whole safety proof for writing in place: the slot w is about to overwrite has already been read by r, so no unread data can ever be destroyed. Every in-place compaction problem in this deck rests on it.

DRILL 02 · RECALL

You are given constraints n ≤ 10⁵ and asked for a contiguous subarray. Which complexity should you be aiming for before you write a single line?

O(n) or O(n log n). At n = 10⁵ a quadratic solution is 10¹⁰ operations against a budget of roughly 10⁸ per second — two orders of magnitude over, every time. Reading the bound first tells you the shape of the answer before you have any idea what the answer is, and it is the single most reliable move in the whole topic. n ≤ 10³ is where the quadratic brute force becomes legal. This budget is not an arrays fact — Sorting (step 02) derives the same 10⁸-per-second figure, and every CONSTRAINTS slide in every deck reads off it.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
08 / WARMUP LOAD THE TOPIC BEFORE UNIT 01 · 2 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · BUG

This is meant to count how many times the maximum appears. It compiles, it runs, and on [3, 7, 7, 2] it returns 1 instead of 2. Which line is wrong?

int best = a[0], cnt = 0;
for (int v : a) {
    if (v > best) { best = v; cnt = 1; }
    if (v == best) cnt++;
}

It must be else if. When v > best fires, it already sets cnt = 1 — and then the very next line sees v == best (because best was just assigned v) and increments it to 2. The count for every fresh maximum is inflated by exactly one, so on [3,7,7,2] you get 7 counted as 3 rather than 2. No crash, no warning, a plausible number. Two independent ifs over the same variable you just mutated is the most reliable way to write a silently wrong single-pass loop.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
09 / INTRO UNIT 01 · The Write Pointer

UNIT 01 — The Write Pointer

Almost every “do it in place” array problem is the same machine: two indices walking one array at different speeds. One reads everything. The other only moves when something is worth keeping. The array behind the write index is the answer; the array ahead of the read index is untouched input; and the region between them is garbage nobody will look at again.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU BUILD A SHORTER ARRAY INSIDE THE ARRAY YOU WERE GIVEN?

IN PLACEWRITE INDEXREAD INDEXINVARIANTRETURNED LENGTH
WHAT TO WATCH FOR
  • 01w STARTS AT 1, NOT 0 — a[0] HAS NOTHING BEFORE IT, SO IT IS A KEEPER BY DEFAULT
  • 02THE COMPARISON IS AGAINST a[w−1], THE LAST KEPT VALUE — NOT AGAINST a[r−1]
  • 03w CAN NEVER OVERTAKE r, WHICH IS WHY OVERWRITING IN PLACE DESTROYS NOTHING
  • 04THE TAIL PAST w IS LEFT AS-IS AND THAT IS CORRECT — THE JUDGE READS ONLY w SLOTS
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
10 / VIDEO UNIT 01 · The Write Pointer

Arrays Intro - Second Largest, Remove Duplicates

STRIVER A2Z
The Write Pointer
RUNTIME 43:26
AFTER THIS → 4 DRILLS · PROBLEM #01
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
11 / DRILL UNIT 01 · The Write Pointer · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

The lecture insists the comparison is a[r] != a[w-1] and not a[r] != a[r-1]. On a SORTED array both happen to work. Why does the lecture still refuse the second one?

a[w-1] is the last value you kept. “Is this a duplicate?” means “does this match what I last committed?” — which is a question about the answer being built, not about the input. On sorted input the two coincide, so the wrong version passes and you learn nothing. The moment the problem changes to “allow each value at most twice” you need a[w-2], and the a[r-1] form has no equivalent at all. Write the version that says what you mean and the variants become one-line edits.

DRILL 02 · TRACE

Run the loop on [1, 1, 2, 2, 2, 3]. At the moment r reaches the last element (value 3), what is w, and what does the array look like?

w = 2, with the array reading [1, 2, 2, 2, 2, 3]. Only 1 and 2 have been committed, so w counts two keepers and slot 1 was overwritten with 2. Everything from index 2 onward is stale — the original values are partly still there and they are meaningless. This is exactly why the visualiser strikes that region out rather than dimming it: it is not “less important” data, it is not data. Step the MECHANISM slide in PREDICT mode and it asks you this every iteration.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
12 / DRILL UNIT 01 · The Write Pointer · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

One line is wrong. It compiles, and on [1,2,3] (no duplicates at all) it returns the right answer. Which line, and what input exposes it?

int w = 1;
for (int r = 1; r < n; r++)
    if (a[r] != a[w - 1])
        a[w] = a[r]; w++;        // commit
return w;

Missing braces. C++ binds only the first statement to the if, so a[w] = a[r] is conditional but w++ is not — w advances on duplicates too. On [1,2,3] every element is kept anyway, so the bug is invisible and the answer is right. Feed it [1,1,2] and it returns 3. This is the archetypal array bug: correct on the input you tested, wrong on the input that mattered, with no crash to tell you.

DRILL 02 · TRANSFER

Same problem, one word changed: each value may now appear at most twice. What is the minimal edit?

Start w at 2 and compare against a[w-2]. That is the whole edit, and it generalises: allow each value at most k times is w = k and a[r] != a[w-k]. The reason it works is the reason drill 1 insisted on a[w-1]: because the comparison is against the answer, asking “is this value already in my last k keepers” is a single index change. LeetCode 80 is this exact problem, and people who wrote the a[r-1] version rewrite it from scratch.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
13 / MECHANISM UNIT 01 · DEDUPE · CODE MIRRORED

THE WRITE POINTER — w IS A COUNT, NOT A POSITION

Two indices walk the same array. r reads every element; w only ever advances when something is kept. Read w as “how many keepers have I committed” rather than “where am I” and two facts fall out for free: a[w-1] is the last keeper, and return w is the answer's length. w can never overtake r, which is the entire proof that overwriting in place is safe.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
14 / PROBLEM #01 · WRITE-POINTER · EASY

Remove Duplicates from Sorted Array

EASY write-pointer ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

The statement hands you the algorithm's two hardest constraints for free: the array is already sorted (so duplicates are adjacent, and you never need a set) and you must return a length rather than an array (so the answer lives in a prefix of the input). Together those two phrases mean “two indices, one pass, O(1) space” before you have read the examples.

INTUITION

Keep a write index w meaning how many unique values I have committed so far. Read across with r. Since the array is sorted, a value is new exactly when it differs from the last one you kept — so compare against a[w−1], commit when they differ, and leave w alone when they match. The values behind w are the answer, the values ahead of r are untouched input, and everything in between is debris that no one will ever read.

STEPS
  1. a[0] is a keeper by definition — nothing precedes it — so start w at 1
  2. Walk r from 1 to n−1, reading every element exactly once
  3. Compare a[r] against a[w−1], the LAST COMMITTED value, never against a[r−1]
  4. When they differ, write a[w] = a[r] and advance w
  5. When they match, advance r alone; w holding still is what drops the duplicate
  6. Return w — it counted the keepers, so it IS the new length
BRUTEO(n log n)
OPTIMALO(n)
↕ SCROLL
// Two indices, one array. r reads everything; w commits only keepers.
// The invariant w <= r is what makes writing in place safe: the slot w
// overwrites was already read by r, so no unread data can be destroyed.
int removeDuplicates(vector<int>& a) {
    if (a.empty()) return 0;
    int w = 1;                          // a[0] is a keeper by definition

    for (int r = 1; r < a.size(); r++)
        if (a[r] != a[w - 1])           // differs from the LAST KEPT value?
            a[w++] = a[r];              // commit it, then widen the answer

    return w;                           // w counted keepers, so it IS the length
}
TIMEO(n)each element is read once and written at most once
SPACEO(1)two integer indices — nothing allocated at all
TRAP

Comparing a[r] != a[r-1] instead of a[r] != a[w-1] passes every test on this problem, because on sorted input the two are the same value. It is still the wrong line, and the cost is deferred: LeetCode 80 (“at most twice”) is a one-character edit from the a[w-1] version and a rewrite from the other one. The second trap is louder — forgetting braces around a[w]=a[r]; w++; makes w advance on duplicates too, which returns a plausible over-count and never crashes.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #01 · REMOVE DUPLICATES FROM SORTED ARRAY

Arrays Intro - Second Largest, Remove Duplicates

The walkthrough for #01 Remove Duplicates from Sorted Array. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Arrays Intro - Second Largest, Remove Duplicates
RUNTIME 43:26
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
15 / INTRO UNIT 02 · Rotation & Compaction

UNIT 02 — Rotation & Compaction

Two problems, one skeleton, and the difference between them is worth more than either. Rotation is index algebra — nothing is compared, the answer is a formula. Move Zeroes is the write pointer again, but swapping rather than copying, because a rearrangement must not lose the values it displaces. Both refuse the obvious temporary array, and both refuse it for the same reason: O(1) space is in the statement.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU REARRANGE AN ARRAY IN PLACE WITHOUT LOSING WHAT YOU OVERWRITE?

IN PLACEREVERSAL TRICKSTABLERELATIVE ORDERO(1) SPACE
WHAT TO WATCH FOR
  • 01k %= n BEFORE ANYTHING ELSE — k IS ALLOWED TO EXCEED n AND USUALLY DOES IN TESTS
  • 02THE THREE REVERSALS ARE ONE WHOLE AND TWO PARTS, IN THAT ORDER
  • 03MOVE ZEROES SWAPS RATHER THAN COPIES, SO THE ZERO IS POSTED FORWARD, NOT LOST
  • 04BOTH PRESERVE RELATIVE ORDER — A BACK-TO-FRONT PARTITION WOULD NOT
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
16 / VIDEO UNIT 02 · Rotation & Compaction

Rotate by K, Union, Intersection, Move Zeros

STRIVER A2Z
Rotation & Compaction
RUNTIME 73:17
AFTER THIS → 4 DRILLS · PROBLEM #02, #03
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
17 / DRILL UNIT 02 · Rotation & Compaction · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does reversing the whole array first, then reversing the two pieces, produce a rotation? What does the first reversal actually accomplish?

The first reversal fixes the blocks, not the elements. Rotating right by k means the last k values must end up in front. Reversing everything does exactly that — the tail block is now at the front and the head block at the back — but each block reads backwards, because reversal reverses. The two inner reversals are therefore corrections, not tricks. Once you see it as “move the blocks, then unscramble each”, you can re-derive it at a whiteboard instead of remembering three lines.

DRILL 02 · TRACE

rotate([1,2,3,4,5,6,7], k = 10). What happens without the k %= n line, and what is the correct output?

a.begin() + 10 on a 7-element vector is an iterator past the end, and passing it to reverse is undefined behaviour — which in practice often does not crash, it just scribbles. With k %= n, k becomes 3 and the answer is [5,6,7,1,2,3,4]. LeetCode 189's constraints explicitly permit k larger than the array, so this is not a theoretical edge case — it is in the tests.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
18 / DRILL UNIT 02 · Rotation & Compaction · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This Move Zeroes variant is O(n), returns an array with all zeros at the end, and is still wrong. What breaks?

int w = 0, r = a.size() - 1;
while (w < r) {
    if (a[w] == 0)
        swap(a[w], a[r--]);   // pull a value from the back
    else w++;
}

It scrambles the order. Swapping from the back moves whatever happens to be at the end into an early slot, so [0,1,0,3,12] can yield [12,1,3,0,0] — every zero correctly at the end, and the non-zeros in the wrong sequence. LeetCode 283 says “maintaining the relative order”, and that clause is the entire reason the forward write-pointer version exists. An O(n) in-place answer that ignores a stability requirement is a wrong answer that looks like a clever one.

DRILL 02 · TRANSFER

Move Zeroes uses swap(a[w++], a[r]) where Remove Duplicates used a[w++] = a[r]. Why can Move Zeroes not simply copy?

Because the two problems return different things. Remove Duplicates returns a length — the tail is explicitly garbage, so overwriting is free. Move Zeroes returns the whole array, so every element must still be present at the end; the zero sitting at slot w has to go somewhere, and the swap posts it to index r, which the reader has already passed. Copy when the tail is disposable, swap when it is not. That one question tells you which form to write.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
19 / MECHANISM UNIT 02 · REVERSE · CODE MIRRORED

ROTATION BY THREE REVERSALS — NO SECOND ARRAY

The naive rotation needs a copy. This does not, and the reason is worth watching rather than memorising: reversing the whole array puts the right blocks in the right places and the wrong order inside each. The two inner reversals are corrections, not tricks. Every element moves exactly twice.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
20 / MECHANISM UNIT 02 · MOVEZERO · CODE MIRRORED

THE SAME POINTER, SWAPPING INSTEAD OF COPYING

Identical skeleton, one change: the keeper test is a property of the value rather than a comparison with its neighbour, and the write is a swap. That matters — the zero displaced from slot w is not destroyed, it is posted forward to index r, which the reader has already passed. Nothing can be re-read, so one pass is enough.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
21 / PROBLEM #02 · INDEX-ALGEBRA · EASY

Rotate Array

EASY index-algebra ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Rotate in place” plus an explicit follow-up asking for O(1) space. Nothing is searched and nothing is compared, so this is not an algorithm problem at all — it is index algebra. The other tell is in the constraints: k is allowed to be far larger than the array, which is the statement quietly telling you to write k %= n.

INTUITION

Rotating right by k means the last k values must end up in front. Reverse the whole array and exactly that happens — the two blocks swap sides — but each block is now internally backwards, because reversal reverses. So reverse each block again to correct it. Three reversals, every element moved exactly twice, no second array anywhere.

STEPS
  1. Take k %= n first — k may exceed n, and a[begin() + k] past the end is undefined
  2. Reverse the entire array: the blocks are now on the correct sides, both backwards
  3. Reverse the first k elements to un-reverse the block that moved to the front
  4. Reverse the remaining n−k to un-reverse the other one
  5. Every element was touched exactly twice, so this is O(n) with no allocation
BRUTEO(n·k)
OPTIMALO(n)
↕ SCROLL
// Reversal rotation. The first reversal puts the right BLOCKS in the
// right places; the other two fix the order inside each block.
void rotate(vector<int>& a, int k) {
    int n = a.size();
    k %= n;                                 // k may exceed n - this is in the tests

    reverse(a.begin(), a.end());            // 1. blocks swap sides, both backwards
    reverse(a.begin(), a.begin() + k);      // 2. fix the block now at the front
    reverse(a.begin() + k, a.end());        // 3. fix the block now at the back
}
TIMEO(n)three reversals over n elements — each element moves exactly twice
SPACEO(1)one temporary per swap; no auxiliary array
TRAP

Omitting k %= n. LeetCode 189's constraints explicitly allow k greater than the array length, and a.begin() + k past the end is undefined behaviour — which usually does not crash. It corrupts quietly, passes your hand-written examples, and fails the judge's larger tests with no indication why. The runner-up trap is reaching for the “cyclic replacement” solution: it is also O(1) space, and it needs a GCD argument to know how many cycles to run. Three reversals need no such argument, which is exactly why they are the version worth memorising.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #02 · ROTATE ARRAY

Rotate by K, Union, Intersection, Move Zeros

The walkthrough for #02 Rotate Array. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Rotate by K, Union, Intersection, Move Zeros
RUNTIME 73:17
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
22 / PROBLEM #03 · WRITE-POINTER · EASY

Move Zeroes

EASY write-pointer ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Two phrases, and the second one is the whole problem. “Move all 0's to the end” sounds like a partition; “while maintaining the relative order of the non-zero elements” forbids the obvious back-to-front partition and forces a forward write pointer. Add “in place, without making a copy” and the shape is fully determined before you see an example.

INTUITION

It is unit 01's write pointer with one change. w marks the oldest slot still owed a non-zero value; r reads everything. When r finds a non-zero, swap it into w and advance both. The swap matters — this is a rearrangement, not a compaction, so the zero displaced from w must survive. It travels to index r, which the reader has already passed, so it can never be examined again.

STEPS
  1. Set w = 0 — the next slot a non-zero value is owed
  2. Walk r across the whole array, reading every element once
  3. When a[r] is zero, do nothing: w stays, so the slot remains owed
  4. When a[r] is non-zero, swap a[w] and a[r], then advance w
  5. Order is preserved because non-zeros are written in the order they were read
  6. When r reaches the end every non-zero is packed and the zeros have all drifted back
BRUTEO(n) time · O(n) space
OPTIMALO(n)
↕ SCROLL
// The write pointer again, but SWAPPING because every element must
// survive: the zero displaced from w travels to r, which is already read.
void moveZeroes(vector<int>& a) {
    int w = 0;                              // oldest slot still owed a non-zero

    for (int r = 0; r < a.size(); r++)
        if (a[r] != 0)
            swap(a[w++], a[r]);             // order kept: written as they were read
}
TIMEO(n)one pass; each element is read once and swapped at most once
SPACEO(1)two integer indices — nothing allocated
TRAP

The tempting O(n) alternative — swap zeros with values pulled from the back — moves fewer elements and destroys the relative order the problem demands. It produces an array with every zero correctly at the end, so it looks right in the debugger and fails the judge. The word doing the work is maintaining, and it is easy to read past. When a rearrangement problem mentions relative order, the forward write pointer is the only shape that preserves it for free.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #03 · MOVE ZEROES

Rotate by K, Union, Intersection, Move Zeros

The walkthrough for #03 Move Zeroes. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Rotate by K, Union, Intersection, Move Zeros
RUNTIME 73:17
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
23 / INTRO UNIT 03 · XOR & The Sum Identity

UNIT 03 — XOR & The Sum Identity

Three sheet rows and one idea underneath two of them: when the noise is paired and the signal is not, something cancels. XOR cancels equal values to zero, and the arithmetic identity n(n+1)/2 cancels a full range against a punctured one. Max Consecutive Ones rides along because it is the same one-pass shape at its very simplest — a running counter and a best-so-far, which is the seed Kadane grows from in unit 07.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE ONE ODD ELEMENT WITHOUT A HASH MAP AND WITHOUT SORTING?

XORPARITYIDENTITY ELEMENTn(n+1)/2RUNNING BEST
WHAT TO WATCH FOR
  • 01XOR IS COMMUTATIVE AND ASSOCIATIVE — WHICH IS EXACTLY WHY ORDER NEVER MATTERS
  • 020 IS XOR'S IDENTITY, SO THE ACCUMULATOR ALWAYS STARTS THERE
  • 03MISSING NUMBER HAS TWO ANSWERS: THE SUM FORMULA AND XOR. ONE OF THEM CAN OVERFLOW
  • 04MAX CONSECUTIVE ONES RESETS THE RUN TO 0, NOT TO 1 — AND THAT IS A REAL DECISION
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
24 / VIDEO UNIT 03 · XOR & The Sum Identity

Appears Once, Missing Number, Max Consecutive 1s

STRIVER A2Z
XOR & The Sum Identity
RUNTIME 38:00
AFTER THIS → 3 DRILLS · PROBLEM #04, #05, #06
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
25 / DRILL UNIT 03 · XOR & The Sum Identity · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does XOR find the single element regardless of how the array is ordered, when a sort-and-scan would need the order fixed first?

Commutativity and associativity. a^b^a = a^a^b = 0^b = b — you may rearrange the folds freely, so a pair cancels whether its two members are adjacent or at opposite ends. That is precisely the property a sort would have had to manufacture, at a cost of O(n log n). This is the general lesson worth taking: when an operation is commutative, order is not information, and any work you do to impose order is wasted.

DRILL 02 · TRACE

Missing Number on [3, 0, 1] using the sum identity. What is computed, and what is the risk that the XOR version does not have?

n(n+1)/2 = 6, actual sum = 4, so the answer is 2. The risk is arithmetic: at n = 10⁵ the expected sum is about 5×10⁹, which is past INT_MAX. The XOR version never grows — every intermediate value stays bounded by the largest element — so it is overflow-proof by construction. Both are O(n) and O(1); the XOR one is simply harder to get wrong, which is a real reason to prefer it and a good thing to say out loud in an interview.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
26 / DRILL UNIT 03 · XOR & The Sum Identity · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

Max Consecutive Ones. This returns 3 on [1,1,0,1,1,1], which happens to be right, and returns 4 on [1,1,1,1], which is not. Which line?

int run = 0, best = 0;
for (int v : a) {
    if (v == 1) run++;
    else run = 0;
    best = max(best, run + 1);
}

The + 1 inflates every answer by one. On [1,1,1,1] it returns 5 for an array of length 4 — and the reason it looked right on the first example is pure coincidence. This is the shape of bug this deck keeps returning to: an off-by-one that produces a plausible number rather than a crash, verified against one example that happened to agree. Also note the reset really is to 0 and not 1: a zero breaks the run entirely, it does not start a new one.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
27 / MECHANISM UNIT 03 · XOR · CODE MIRRORED

XOR — THE BITS ARE THE ARGUMENT

Watch the columns, not the values. XOR is parity per bit position: a column with an even number of 1s zeroes out. Since every value except one appears twice, every column's contribution cancels except the loner's. It is commutative and associative, which is exactly why the array never needs sorting and why the answer is order-independent.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
28 / PROBLEM #04 · XOR-IDENTITY · EASY

Missing Number

EASY xor-identity ▶ SOLVE ON LEETCODEReturns in Bit Manipulation, solved by XOR-ing indices against values instead of by the sum identity.
SIGNAL — WHAT GIVES IT AWAY

“Contains n distinct numbers in the range [0, n]” — a known, complete range with exactly one hole. That phrasing is the signal for a cancellation identity: either XOR everything against every index, or subtract the actual sum from the closed-form expected sum. The follow-up asking for O(1) space rules out the seen-array that everyone reaches for first.

INTUITION

You know exactly what the array should have contained: 0 through n. So compare the full range against what is actually there and whatever fails to cancel is the missing value. XOR does this without ever growing — fold in every index and every element, and each present value appears exactly twice and vanishes. The sum identity n(n+1)/2 does the same job with arithmetic, at the price of an intermediate that can overflow.

STEPS
  1. Note that indices 0..n and values 0..n differ by exactly one missing element
  2. Fold every index i from 0 to n into an accumulator x
  3. Fold every array element into the same x
  4. Every present value has now been XOR-ed exactly twice and cancelled to 0
  5. x holds the one value that was folded once — the missing number
  6. Alternative: n(n+1)/2 minus the actual sum, but watch the overflow
BRUTEO(n log n)
OPTIMALO(n)
↕ SCROLL
// XOR the full range against what is actually present. Every value
// that IS there gets folded twice and cancels; the hole is folded once.
// Preferred over n(n+1)/2 because no intermediate can ever overflow.
int missingNumber(vector<int>& a) {
    int n = a.size(), x = 0;

    for (int i = 0; i < n; i++)
        x ^= i ^ a[i];                      // index and value, both folded
    return x ^ n;                           // index n has no element to pair with
}
TIMEO(n)a single fold over indices and values
SPACEO(1)one accumulator, which never grows past the largest element
TRAP

The sum version, n*(n+1)/2 - sum(a), is correct mathematics and an overflow waiting to happen: at n = 10⁵ the expected sum is about 5×10⁹, comfortably past INT_MAX. In C++ it wraps and returns a plausible wrong number with no warning; in Python it is fine, which is exactly how the habit survives long enough to fail in an interview. The XOR version is bounded by the largest element by construction — same complexity, no arithmetic hazard, and it generalises directly to Single Number.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #04 · MISSING NUMBER

Appears Once, Missing Number, Max Consecutive 1s

The walkthrough for #04 Missing Number. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Appears Once, Missing Number, Max Consecutive 1s
RUNTIME 38:00
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
29 / PROBLEM #05 · COUNTER · EASY

Max Consecutive Ones

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

The cheapest problem in the deck, and worth doing attentively because it is the seed of Kadane. “Maximum number of consecutive 1's” is a running quantity plus a best-so-far — the exact shape unit 07 generalises to arbitrary sums. If you can state why the reset is to 0 and not 1, you already understand the harder version.

INTUITION

Carry a run length. Every 1 extends it by one; every 0 ends it, so it resets to zero. Record the best after every element rather than only at the zeros — otherwise a run that reaches the end of the array is never recorded at all, which is the classic off-by-one here.

STEPS
  1. Keep two integers: run (the current streak) and best (the largest seen)
  2. For each value, if it is 1 then run++, otherwise run = 0
  3. Update best = max(best, run) on EVERY element, not only when the run breaks
  4. Return best — a run of length 0 is correct for an array with no 1s
BRUTEO(n²)
OPTIMALO(n)
↕ SCROLL
// The seed of Kadane: a running quantity and a best-so-far.
// best is updated on EVERY element, so a streak that runs to the end
// of the array is still recorded.
int findMaxConsecutiveOnes(vector<int>& a) {
    int run = 0, best = 0;

    for (int v : a) {
        run = (v == 1) ? run + 1 : 0;       // a 0 ends the streak entirely
        best = max(best, run);              // record here, not at the break
    }
    return best;
}
TIMEO(n)one pass, one comparison per element
SPACEO(1)two integers
TRAP

Updating best only when a 0 is met loses any streak that reaches the end of the array — [1,1,1] returns 0. It is invisible on every example that happens to end in a zero, which is most hand-written ones. The mirror-image mistake is resetting run = 1 instead of 0 on a zero: a zero breaks the run, it does not start a new one, and that version over-counts by one on every streak.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #05 · MAX CONSECUTIVE ONES

Appears Once, Missing Number, Max Consecutive 1s

The walkthrough for #05 Max Consecutive Ones. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Appears Once, Missing Number, Max Consecutive 1s
RUNTIME 38:00
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
30 / PROBLEM #06 · XOR-IDENTITY · EASY

Single Number

EASY xor-identity ▶ SOLVE ON LEETCODEReturns in Bit Manipulation, where the XOR identity that makes this work is the lesson rather than a trick.
SIGNAL — WHAT GIVES IT AWAY

“Every element appears twice except for one” plus “linear runtime and constant extra space”. The pairing is stated outright and the space bound rules out both the hash set and the sort. Once a problem tells you the noise is even and the signal is odd, XOR is not a trick you recall — it is the definition of what you were asked for.

INTUITION

XOR is parity, computed independently in every bit position. Folding the whole array into one accumulator, each bit column counts how many 1s appeared there, modulo two. Every duplicated value contributes to each column an even number of times and therefore contributes nothing. What remains, column by column, is exactly the bits of the single element.

STEPS
  1. Start an accumulator at 0 — XOR's identity, so 0 ^ v is always v
  2. Fold every element into it with x ^= v
  3. Each paired value cancels itself: v ^ v is 0, wherever the two sit
  4. Order never mattered, because XOR is commutative and associative
  5. Return x — the only value folded an odd number of times
BRUTEO(n) time · O(n) space
OPTIMALO(n)
↕ SCROLL
// XOR is parity per bit column. A value appearing twice contributes
// an even number of 1s to every column, so it contributes nothing at all.
// Order is irrelevant: XOR is commutative AND associative.
int singleNumber(vector<int>& a) {
    int x = 0;                              // 0 is XOR's identity

    for (int v : a)
        x ^= v;                             // equal pairs cancel to 0
    return x;                               // only the loner survives
}
TIMEO(n)one fold over the array
SPACEO(1)one accumulator, no map and no sort
TRAP

This one rarely goes wrong; the trap is where the pattern stops working. XOR needs the noise to be paired. If every other element appears three times (LeetCode 137), pairs never form and a plain fold returns garbage — you need per-bit counting modulo 3 instead. If there are two single elements (LeetCode 260), the fold gives you their XOR and you must split the array on any set bit of it. Knowing the boundary of a pattern is worth more than knowing the pattern.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #06 · SINGLE NUMBER

Appears Once, Missing Number, Max Consecutive 1s

The walkthrough for #06 Single Number. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Appears Once, Missing Number, Max Consecutive 1s
RUNTIME 38:00
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
31 / INTRO UNIT 04 · The Hashmap Complement

UNIT 04 — The Hashmap Complement

The single most important reframe in the whole topic. “Do two values sum to t?” is a question about pairs, and there are n²/2 of them. “Have I already seen t − a[i]?” is a question about one value, and a hash map answers it in O(1). Nothing about the array changed; the question changed, and the complexity followed. Almost every O(n²) → O(n) improvement in this deck is that same move.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND A PAIR WITHOUT EVER LOOKING AT A PAIR?

COMPLEMENTHASH MAPO(1) LOOKUPSPACE-TIME TRADEONE PASS
WHAT TO WATCH FOR
  • 01CHECK FIRST, STORE SECOND — REVERSING THEM LETS AN ELEMENT PAIR WITH ITSELF
  • 02THE MAP KEYS ARE VALUES AND THE VALUES ARE INDICES, NOT THE OTHER WAY ROUND
  • 03THE PROBLEM ASKS FOR INDICES, WHICH IS PRECISELY WHY YOU MAY NOT SORT
  • 04ONE PASS IS ENOUGH — YOU NEVER NEED TO BUILD THE MAP FIRST AND THEN SCAN
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
32 / VIDEO UNIT 04 · The Hashmap Complement

2 Sum - Two Types of the Same Problem

STRIVER A2Z
The Hashmap Complement
RUNTIME 18:20
AFTER THIS → 3 DRILLS · PROBLEM #07
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
33 / DRILL UNIT 04 · The Hashmap Complement · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

The lecture stresses storing a[i] after checking for its complement. What goes wrong if you store first?

The element pairs with itself. With a = [3, 5] and t = 6, storing 3 before checking means the lookup for 6 − 3 = 3 succeeds immediately and you return [0, 0] — a single element used twice, which the problem forbids. Checking first guarantees the map only ever contains strictly earlier indices, so any hit is a genuine pair. The invariant is “the map holds the past, never the present”, and one line's ordering enforces it.

DRILL 02 · TRACE

On [2, 7, 11, 15, 3, 6] with target 21, at which index does the algorithm return, and what is in the map at that moment?

i = 5. Walking through: 2 needs 19 (absent), 7 needs 14 (absent), 11 needs 10 (absent), 15 needs 6 (absent — 6 has not been reached yet), 3 needs 18 (absent), and finally 6 needs 15, which was stored at index 3. The answer is [3, 5]. Notice that the pair was half discovered at i = 3 and completed at i = 5 — the map is what carries that half-knowledge forward, which is why one pass suffices. This is the exact trace the MECHANISM slide walks.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
34 / DRILL UNIT 04 · The Hashmap Complement · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

The array is already sorted and the problem asks for the two values, not their indices. What changes, and why is it now better?

Two pointers, O(1) space. This is LeetCode 167, and the difference is the whole reason both problems exist. The hash map buys O(1) lookup at a cost of O(n) memory; a sorted array already gives you a decidable move at every step, so you pay nothing. And the reason Two Sum itself cannot do this is the word indices — sorting would destroy them. Read the output type before choosing the technique: “return indices” forbids sorting, “return values” permits it, and that single word decides your space complexity.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
35 / MECHANISM UNIT 04 · HASH · CODE MIRRORED

THE MAP TURNS A QUESTION ABOUT PAIRS INTO A QUESTION ABOUT ONE VALUE

“Do two values sum to t?” is a question about pairs, and there are n²/2 of them. “Have I already seen t − a[i]?” is a question about one value, and a hash map answers it in O(1). That reframe is the entire algorithm — everything else is bookkeeping. Watch the order of the two operations: check, then store. Storing first lets an element pair with itself.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
36 / PROBLEM #07 · HASHING · MED

Two Sum

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

“Return the indices of the two numbers” — and that one word is doing enormous work. It forbids sorting, which is what would otherwise buy you two pointers and O(1) space. With sorting off the table and the array unordered, the only way to beat O(n²) is to buy O(1) lookup with memory, which means a hash map.

INTUITION

Stop asking about pairs. For the current element there is exactly one value that could complete it: t − a[i]. So the real question is “have I already walked past that value?” — a question about a single number, which a hash map answers instantly. Walk once, and for each element check for its complement before storing itself, so the map only ever holds strictly earlier indices.

STEPS
  1. Create an empty map from value to the index where it was seen
  2. Walk i across the array once
  3. Compute need = t − a[i], the only partner that can work for this element
  4. If need is already in the map, return {map[need], i} — done
  5. Otherwise store a[i] → i and continue
  6. Check BEFORE storing, or an element whose double is t will pair with itself
BRUTEO(n²)
OPTIMALO(n)
↕ SCROLL
// The reframe: "do two values sum to t" is a question about PAIRS.
// "Have I seen t - a[i]" is a question about ONE value, and a map answers
// that in O(1). Check BEFORE storing, or a[i] pairs with itself.
vector<int> twoSum(vector<int>& a, int t) {
    unordered_map<int, int> seen;           // value -> index it was seen at

    for (int i = 0; i < a.size(); i++) {
        int need = t - a[i];                // the ONE partner that works
        if (seen.count(need))               // has it already gone past?
            return {seen[need], i};
        seen[a[i]] = i;                     // store AFTER checking
    }
    return {};
}
TIMEO(n)one pass with O(1) expected lookup per element
SPACEO(n)the map holds at most n entries — this is the trade being made
TRAP

Storing before checking. With a = [3, 5] and t = 6, storing 3 first means the lookup for 3 succeeds against the element you are standing on, and you return [0, 0] — one element used twice. The check-then-store order makes it impossible by construction, because the map then holds only strictly earlier indices. The other trap is reflexive sorting: it is the right instinct on almost every other problem in this deck, and here it destroys the very thing you were asked to return.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #07 · TWO SUM

2 Sum - Two Types of the Same Problem

The walkthrough for #07 Two Sum. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
2 Sum - Two Types of the Same Problem
RUNTIME 18:20
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
37 / INTRO UNIT 05 · Dutch National Flag

UNIT 05 — Dutch National Flag

Sorting three distinct values does not need a sort. Keep three regions and one unclassified gap, and each element is examined once. The invariant is a claim about index ranges rather than elements: everything before low is a 0, everything before mid is a 1, everything after high is a 2, and the gap between mid and high has never been looked at. Every line of the algorithm follows from protecting that sentence.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU SORT THREE VALUES IN ONE PASS, WITHOUT COUNTING THEM FIRST?

INVARIANTTHREE-WAY PARTITIONUNCLASSIFIED REGIONONE PASSDUTCH FLAG
WHAT TO WATCH FOR
  • 01ON A 2, mid DOES *NOT* ADVANCE — THAT IS THE LINE PEOPLE GET WRONG
  • 02ON A 0, BOTH low AND mid ADVANCE, AND THE REASON IS WORTH HEARING
  • 03THE LOOP CONDITION IS mid <= high, NOT mid < high
  • 04COUNTING SORT IS TWO PASSES AND ALSO O(n) — THE ONE-PASS CONSTRAINT IS WHY THIS EXISTS
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
38 / VIDEO UNIT 05 · Dutch National Flag

Sort an Array of 0s, 1s and 2s

STRIVER A2Z
Dutch National Flag
RUNTIME 25:07
AFTER THIS → 3 DRILLS · PROBLEM #08
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
39 / DRILL UNIT 05 · Dutch National Flag · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

On a[mid] == 2 you swap with high and decrement it — but you do not advance mid. Why not?

The incoming value is unknown. Everything after high is known to be a 2, but a[high] itself sits inside the unclassified region — nobody has looked at it. Advancing mid would step straight over an element you have never examined, and if it is a 0 it is now stranded in the middle region forever. Contrast the 0 case: the value coming back from low was already classified as a 1, so it needs no second look and mid may safely advance. The rule is not about 0s and 2s; it is about whether the incoming value has been seen.

DRILL 02 · TRACE

Trace [2, 0, 1] with low = mid = 0, high = 2. What are the first two actions?

Swap to the back, then reclassify the same slot. The 2 goes to index 2 and the 1 comes forward, giving [1,0,2] with high = 1. Because mid stayed at 0, the very next step examines that freshly-arrived 1 — which is exactly the point of not advancing. Had mid moved, the 1 would sit unclassified in front of low, and the final array would be wrong while still looking plausibly grouped. Step the MECHANISM slide in PREDICT mode; it asks you which cell each swap targets.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
40 / DRILL UNIT 05 · Dutch National Flag · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This uses mid < high as the loop condition. On which kind of input does it silently fail?

while (mid < high) {
    if (a[mid] == 0) swap(a[low++], a[mid++]);
    else if (a[mid] == 1) mid++;
    else swap(a[mid], a[high--]);
}

The last element is skipped. When mid and high meet there is still exactly one unclassified slot, and mid < high exits before examining it. On [1, 0] you get [1, 0] back — untouched and wrong. It passes plenty of tests, because the final element is often already in the right place by luck. The unclassified region is [mid, high] inclusive at both ends, so the loop must be <=. Write the invariant down and the loop condition is not a guess.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
41 / MECHANISM UNIT 05 · DUTCH · CODE MIRRORED

THREE REGIONS, ONE PASS — AND mid DOES NOT ALWAYS MOVE

The invariant is a claim about ranges: everything before low is 0, everything before mid is 1, everything after high is 2, and the gap between mid and high has never been looked at. That last clause is why swapping a 2 to the back leaves mid where it is: the value that came back is unclassified, so advancing past it would skip an element unseen.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
42 / PROBLEM #08 · PARTITION · MED

Sort Colors

MED partition ▶ SOLVE ON LEETCODEReturns in Sorting as the Dutch national flag partition — the same three-region pass, framed as a sorting primitive.
SIGNAL — WHAT GIVES IT AWAY

“Sort an array of 0s, 1s and 2s” with the follow-up “one pass, constant space, without using the library sort”. A tiny fixed value set plus a one-pass constraint is the Dutch-flag signature. Counting sort also solves it in O(n) but needs two passes — the one-pass clause exists specifically to rule that out.

INTUITION

Maintain three regions and one gap. Everything before low is 0, everything before mid is 1, everything after high is 2, and the stretch from mid to high has never been examined. Read a[mid] and place it: a 0 swaps to the front and both low and mid advance; a 1 is already home so only mid advances; a 2 swaps to the back and high shrinks — but mid holds still, because the value that just arrived from high has never been looked at.

STEPS
  1. Set low = 0, mid = 0, high = n−1
  2. Loop while mid <= high — inclusive, because [mid, high] is the unclassified range
  3. a[mid] == 0 → swap(a[low], a[mid]), then low++ and mid++
  4. a[mid] == 1 → mid++ only; it is already in the middle region
  5. a[mid] == 2 → swap(a[mid], a[high]), then high-- and leave mid ALONE
  6. The loop ends when the unclassified region is empty
BRUTEO(n log n)
OPTIMALO(n)
↕ SCROLL
// Three regions and one unclassified gap [mid, high]. The invariant
// is a claim about RANGES, and every line below protects it.
void sortColors(vector<int>& a) {
    int low = 0, mid = 0, high = a.size() - 1;

    while (mid <= high) {                   // <= : [mid, high] is INCLUSIVE
        if (a[mid] == 0) swap(a[low++], a[mid++]);      // both advance
        else if (a[mid] == 1) mid++;                    // already home
        else swap(a[mid], a[high--]);                   // mid does NOT move
    }
}
TIMEO(n)every element is classified exactly once in a single pass
SPACEO(1)three indices; the swaps are in place
TRAP

Advancing mid after swapping a 2 to the back. The value pulled in from high is unclassified — nobody has looked at it — so stepping over it can strand a 0 in the middle region permanently. Contrast the 0 case, where the value returned from low is known to be a 1 and needs no second look. The second trap is writing mid < high: the unclassified range is inclusive at both ends, so the final element is never examined and [1,0] comes back untouched.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #08 · SORT COLORS

Sort an Array of 0s, 1s and 2s

The walkthrough for #08 Sort Colors. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Sort an Array of 0s, 1s and 2s
RUNTIME 25:07
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
43 / INTRO UNIT 06 · Boyer-Moore Voting

UNIT 06 — Boyer-Moore Voting

A genuinely surprising algorithm: it finds the majority element in one pass with a single counter and no memory of what it has seen. The idea is annihilation — pair up two unequal elements and discard both. If one value holds more than half the array it cannot be fully cancelled, so it must be what survives. But read that carefully: it is the only possible survivor, not proof that a majority exists. That gap is where the second pass lives, and it is not optional.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND A VALUE OCCURRING MORE THAN n/2 TIMES USING ONE COUNTER?

ANNIHILATIONCANDIDATEVERIFICATIONn/2 THRESHOLDO(1) SPACE
WHAT TO WATCH FOR
  • 01THE SURVIVOR IS A CANDIDATE, NEVER AN ANSWER — THE VERIFY PASS IS THE ALGORITHM
  • 02COUNT HITTING 0 MEANS THE PREFIX CANCELLED EXACTLY AND THE PROBLEM RESTARTS THERE
  • 03THE CANDIDATE VARIABLE IS MEANINGLESS WHENEVER THE COUNT IS 0 — DO NOT READ IT
  • 04TWO PASSES OF O(n) IS STILL O(n); THE VERIFY COSTS NOTHING ASYMPTOTICALLY
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
44 / VIDEO UNIT 06 · Boyer-Moore Voting

Majority Element - Moore's Voting Algorithm

STRIVER A2Z
Boyer-Moore Voting
RUNTIME 18:13
AFTER THIS → 3 DRILLS · PROBLEM #09
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
45 / DRILL UNIT 06 · Boyer-Moore Voting · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Run the voting loop on [1, 2, 3], which has no majority element. What does it produce, and what does that tell you?

It proposes 3, with a count of 1 and total confidence. Trace it: 1 is adopted, 2 cancels it to zero, 3 is adopted because the count is zero. The algorithm has no way to know it failed. This is the single most important sentence about Boyer–Moore: it identifies the only value that COULD be a majority, and says nothing about whether one exists. LeetCode 169 guarantees a majority so the missing verify pass never bites — and then the same habit walks straight into 229, where it produces wrong answers.

DRILL 02 · TRACE

On [2, 2, 1, 3, 2], what are the candidate and count after the fourth element (value 3)?

Candidate 2, count 0. Step through: 2 adopted (count 1), 2 agrees (count 2), 1 disagrees (count 1), 3 disagrees (count 0). The candidate variable still reads 2, but with a count of zero it carries no information at all — the prefix [2,2,1,3] has cancelled exactly, two against two. The fifth element then re-adopts 2, and it happens to be correct. Whenever the count is 0, ignore the candidate: it is stale, not a partial answer.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
46 / DRILL UNIT 06 · Boyer-Moore Voting · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This drops the verification because “the problem guarantees a majority exists”. On LeetCode 169 that reasoning holds. Where does the habit actually cost you?

int cand = 0, cnt = 0;
for (int v : a) {
    if (cnt == 0) cand = v;
    cnt += (v == cand) ? 1 : -1;
}
return cand;                    // no verify

On 169 alone this is defensible. The cost is the habit: its twin, LeetCode 229, asks for values appearing more than ⌊n/3⌋ times and makes no guarantee that any exist — so the two surviving candidates must both be counted, and frequently neither qualifies. That problem is deck 2's unit 02. Write the verify pass even when it is provably redundant, because what you are really learning is that voting produces candidates, and that is the part that transfers.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
47 / MECHANISM UNIT 06 · VOTE · CODE MIRRORED

VOTING FINDS A CANDIDATE, NEVER AN ANSWER

Unequal elements annihilate in pairs. A value holding more than half the array cannot be fully cancelled, so it is the only possible survivor — and the emphasis belongs on possible. Run this on [1,2,3] and it proposes 3 with complete confidence. The verification pass is not a safety check bolted on afterwards; it is the half of the algorithm that turns a candidate into an answer.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
48 / PROBLEM #09 · VOTING · MED

Majority Element

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

More than ⌊n/2⌋ times” is a threshold, and a threshold is what makes cancellation work: at most one such value can exist, so pairing off unequal elements cannot destroy it. Pair that with a follow-up asking for O(1) space — which rules out the frequency map everybody writes first — and Boyer–Moore is the only remaining shape.

INTUITION

Imagine deleting two unequal elements at a time. Each deletion removes at most one copy of the majority value and at least one non-majority value, so a value holding strictly more than half can never be exhausted first. One counter simulates this: hold a candidate, increment when an element agrees, decrement when it disagrees, and adopt a new candidate whenever the counter reaches zero — because a zero means the prefix cancelled exactly and the problem restarts from there.

STEPS
  1. Keep a candidate and a count, both initially meaningless
  2. For each value: if count is 0, adopt this value as the candidate
  3. Increment count when the value equals the candidate, decrement otherwise
  4. After the pass the candidate is the ONLY possible majority element
  5. Second pass: count the candidate's actual occurrences
  6. Return it only if that count exceeds n/2 — otherwise no majority exists
BRUTEO(n) time · O(n) space
OPTIMALO(n)
↕ SCROLL
// Unequal elements annihilate in pairs. A value holding more than half
// cannot be fully cancelled, so it is the ONLY POSSIBLE survivor -- which
// is not the same as proof that it IS the majority. Hence pass two.
int majorityElement(vector<int>& a) {
    int cand = 0, cnt = 0;

    for (int v : a) {
        if (cnt == 0) cand = v;             // prefix cancelled: restart here
        cnt += (v == cand) ? 1 : -1;        // agree, or annihilate
    }
    int c = 0;
    for (int v : a) c += (v == cand);       // VERIFY -- this IS the algorithm
    return c > a.size() / 2 ? cand : -1;
}
TIMEO(n)two sequential passes, both linear — still O(n)
SPACEO(1)a candidate and a counter, nothing else
TRAP

Skipping the verification pass. LeetCode 169 guarantees a majority exists, so the shortened version is accepted and the habit forms. Then [1,2,3] confidently returns 3, and its twin — Majority Element II at ⌊n/3⌋, which makes no such guarantee — starts producing wrong answers for a reason that is no longer visible. The second trap is reading the candidate while the count is zero: at that moment the variable is stale, not a partial answer.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #09 · MAJORITY ELEMENT

Majority Element - Moore's Voting Algorithm

The walkthrough for #09 Majority Element. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Majority Element - Moore's Voting Algorithm
RUNTIME 18:13
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
49 / INTRO UNIT 07 · Kadane's Algorithm

UNIT 07 — Kadane's Algorithm

Kadane is not a formula, it is one question asked once per element: is the run I am carrying still worth extending, or is this element better off starting fresh? Keep two quantities apart — cur, the best run ending exactly here, and best, the best run seen anywhere — and the algorithm writes itself. Best Time to Buy and Sell Stock is the same loop with the running quantity renamed, which is why it sits in this unit.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE BEST CONTIGUOUS RUN WITHOUT TRYING EVERY RUN?

RUNNING BESTRESETCONTIGUOUSPREFIX THAT HURTSO(1) SPACE
WHAT TO WATCH FOR
  • 01cur AND best ARE DIFFERENT THINGS — COLLAPSING THEM INTO ONE VARIABLE BREAKS IT
  • 02BOTH START AT a[0], NOT AT 0 — A SUBARRAY MUST BE NON-EMPTY
  • 03A PREFIX IS DROPPED WHEN IT HURTS, WHICH IS THE ONLY DECISION MADE
  • 04THE STOCK PROBLEM IS THIS LOOP WITH 'CHEAPEST SO FAR' IN PLACE OF 'RUN SO FAR'
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
50 / VIDEO UNIT 07 · Kadane's Algorithm

Kadane's Algorithm - Maximum Subarray Sum

STRIVER A2Z
Kadane's Algorithm
RUNTIME 20:09
AFTER THIS → 4 DRILLS · PROBLEM #10, #11
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
51 / DRILL UNIT 07 · Kadane's Algorithm · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why must best be initialised to a[0] rather than to 0?

All-negative input. On [-3, -1, -2] the correct answer is −1, the least-bad single element. Initialising best = 0 returns 0 — the sum of the empty subarray, which the problem does not allow. It is invisible on every example containing a positive number, which is most hand-written ones, and LeetCode 53's test suite very much includes the negative case. Initialise from the data, not from a convenient constant.

DRILL 02 · TRACE

Run Kadane on [-2, 1, -3, 4, -1, 2, 1, -5, 4]. At i = 3 (value 4), what happens to cur, and why?

It restarts at 4. After index 2 the carried cur is −2 (from 1 + (−3)). Extending gives −2 + 4 = 2; starting over gives 4. Four wins, so the whole prefix is discarded. This is the moment the algorithm's one decision is visible, and it is also where the final answer's subarray begins — the run [4, −1, 2, 1] summing to 6. Step the MECHANISM slide in PREDICT mode; it asks you this at every element.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
52 / DRILL UNIT 07 · Kadane's Algorithm · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This tracks only one variable instead of two. It returns 6 on the standard example, which is correct. Where does it fail?

int cur = a[0];
for (int i = 1; i < n; i++)
    cur = max(a[i], cur + a[i]);
return cur;                     // no best

cur answers “best run ending here”, and returning it after the loop answers “best run ending at the last index”. On [5, -10, 3] it returns 3 when the answer is 5. It happens to be right on the standard example only because that example's best run reaches near the end. Two variables answer two different questions, and the second one — record the maximum as you go — is the half people drop.

DRILL 02 · TRANSFER

Best Time to Buy and Sell Stock is this same loop. What replaces cur, and what replaces the extend-or-restart test?

The running quantity changes and the skeleton does not. Track the cheapest price seen; the profit available today is price[i] − cheapest, and you record the best of those exactly as Kadane records best. The reset — “today is the new cheapest” — is the same drop-a-prefix-that-hurts move. Measured as a code diff it changes four lines, which is why the next slide renders it as a diff rather than as a fresh solution: seeing those two lines swap is the whole lesson.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
53 / MECHANISM UNIT 07 · KADANE · CODE MIRRORED

ONE DECISION, ASKED ONCE PER ELEMENT

Kadane is not a formula to memorise, it is a single question repeated: is the run I am carrying still worth extending, or is this element better off starting fresh? Everything else follows. Watch the two quantities stay separate — cur is the best run ending here, best is the best run anywhere so far, and collapsing them into one variable is the most common way to break it.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
54 / PROBLEM #10 · KADANE · MED

Maximum Subarray

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

Contiguous subarray” plus “largest sum”. Contiguous means position matters and you may not sort or reorder anything; largest means you are optimising, not counting. That pair is Kadane's exact signature. If the word had been “subsequence” this would be a different problem entirely, and if it had been “count” it would be prefix sums.

INTUITION

Walk once and carry one number: the best sum of a subarray that ends exactly at the current element. At each step there are only two candidates for that number — extend the previous run by this element, or start a new run here — so take the better. Separately record the largest value that quantity has ever taken. The insight is that a prefix with a negative sum can only ever hurt whatever follows, so the moment carrying it costs more than dropping it, you drop it.

STEPS
  1. Set cur = best = a[0]; a subarray must be non-empty, so never start at 0
  2. For each later element, compute max(a[i], cur + a[i])
  3. That is the whole decision: start fresh here, or extend what you were carrying
  4. Assign the winner to cur — the best run ending at this index
  5. Update best = max(best, cur) on every element, not only at resets
  6. Return best — cur alone is the best run ending at the LAST index, which is different
BRUTEO(n²)
OPTIMALO(n)
↕ SCROLL
// One decision per element: extend the run, or start over here.
// cur  = best run ENDING AT i.   best = best run ANYWHERE so far.
// Collapsing those two into one variable is how this usually breaks.
int maxSubArray(vector<int>& a) {
    int cur = a[0], best = a[0];            // NOT 0 - handles all-negative input

    for (int i = 1; i < a.size(); i++) {
        cur  = max(a[i], cur + a[i]);       // a prefix that hurts is dropped
        best = max(best, cur);              // record on EVERY element
    }
    return best;
}
TIMEO(n)one pass, one comparison and one max per element
SPACEO(1)two integers, regardless of input size
TRAP

Initialising best = 0 returns 0 on an all-negative array, where the correct answer is the least-bad single element. It is invisible on every example containing a positive, and LeetCode 53 tests it. The second trap is returning cur instead of best: cur answers “best run ending at the last index”, so on [5, -10, 3] it returns 3 rather than 5. Both produce a plausible number and neither crashes.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #10 · MAXIMUM SUBARRAY

Kadane's Algorithm - Maximum Subarray Sum

The walkthrough for #10 Maximum Subarray. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Kadane's Algorithm - Maximum Subarray Sum
RUNTIME 20:09
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
55 / PROBLEM #11 · KADANE · MED

Best Time to Buy and Sell Stock

MED kadane ▶ SOLVE ON LEETCODEKadane on the difference array. The running value becomes today's price minus the cheapest price so far, and the reset becomes 'today is the new cheapest' -- same two lines, different quantity.
SIGNAL — WHAT GIVES IT AWAY

“Buy on one day, sell on a later day” is an ordering constraint, which is what makes this a one-pass problem rather than a max-minus-min. You cannot simply take the largest price minus the smallest, because the smallest might come afterwards. What you need at each day is the cheapest price so far — a running quantity, exactly like Kadane's.

INTUITION

This is the previous problem with the running quantity renamed. Instead of carrying the best run ending here, carry the cheapest price seen so far. Today's best possible profit is then today's price minus that cheapest, and the answer is the largest such value over the whole walk. The reset — “today is the new cheapest” — is the same drop-a-prefix-that-hurts move, since a higher earlier price can never help a later sale.

STEPS
  1. Set cheap = a[0] and best = 0; zero profit is always achievable by not trading
  2. For each later day, first update cheap = min(cheap, a[i])
  3. Then compute a[i] − cheap, the best profit achievable selling today
  4. Keep best = max(best, that value)
  5. Return best — the ordering constraint is honoured because cheap only ever looks backwards
BRUTEO(n²)
OPTIMALO(n)
-int maxSubArray(vector<int>& a) {-    int cur = a[0], best = a[0];            // NOT 0 - handles all-negative input+int maxProfit(vector<int>& a) {+    int cheap = a[0], best = 0;             // 0: not trading is always allowed       for (int i = 1; i < a.size(); i++) {-        cur  = max(a[i], cur + a[i]);       // a prefix that hurts is dropped-        best = max(best, cur);              // record on EVERY element+        cheap = min(cheap, a[i]);           // the best day to have bought+        best  = max(best, a[i] - cheap);    // record before moving on     }     return best; }
TIMEO(n)one pass, one min and one max per day
SPACEO(1)two integers
TRAP

Updating best before cheap. If you compute the profit first and only then lower the cheapest price, a single-day array or a strictly decreasing run can produce a profit from buying and selling on the same day. Update cheap first and the ordering constraint enforces itself. The other trap is max(a) − min(a), which ignores order entirely and returns a profit for a trade you could not have made — on [7, 6, 4, 3, 1] it reports 6 where the answer is 0.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #11 · BEST TIME TO BUY AND SELL STOCK

DP 35 - Best Time to Buy and Sell Stock

The walkthrough for #11 Best Time to Buy and Sell Stock. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
DP 35 - Best Time to Buy and Sell Stock
RUNTIME 9:11
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
56 / INTRO UNIT 08 · Rearrange by Sign

UNIT 08 — Rearrange by Sign

The write pointer again, doubled. Two destination cursors start at 0 and 1 and each advances by two, so positives fill the even slots and negatives the odd ones without either cursor ever colliding with the other. No bounds check, no interleaving logic — the arithmetic does the work. Relative order survives for free, because values are written in exactly the order they were read.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU ALTERNATE TWO CATEGORIES WITHOUT SHUFFLING ANYTHING?

ALTERNATEEVEN/ODD SLOTSSTABLETWO CURSORSO(n) SPACE
WHAT TO WATCH FOR
  • 01THE TWO CURSORS STEP BY TWO, WHICH IS WHY THEY CAN NEVER MEET
  • 02THE EASY VARIANT GUARANTEES EQUAL COUNTS — THE HARD ONE DOES NOT, AND THAT CHANGES EVERYTHING
  • 03WRITING IN READ ORDER IS WHAT PRESERVES RELATIVE ORDER WITHIN EACH SIGN
  • 04THIS COSTS O(n) EXTRA SPACE, AND THE LECTURE IS HONEST THAT IN-PLACE IS MUCH HARDER
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
57 / VIDEO UNIT 08 · Rearrange by Sign

Rearrange Array Elements by Sign

STRIVER A2Z
Rearrange by Sign
RUNTIME 21:37
AFTER THIS → 3 DRILLS · PROBLEM #12
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
58 / DRILL UNIT 08 · Rearrange by Sign · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why can the two destination cursors never collide, with no bounds check between them?

Even and odd are disjoint sets. pos starts at 0 and adds 2 forever, so it visits 0, 2, 4…; neg starts at 1 and visits 1, 3, 5…. No value is in both sequences, so no slot can be written twice — and that is a property of the arithmetic, not something the code has to check. This is a small but general idea: choose an indexing scheme that makes the invariant impossible to violate and you delete the guard clause entirely.

DRILL 02 · TRACE

On [3, 1, -2, -5, 2, -4], what is the result, and where does the value 1 end up?

[3, −2, 1, −5, 2, −4], with 1 at index 2. The positives in read order are 3, 1, 2 and they take slots 0, 2, 4; the negatives are −2, −5, −4 and they take 1, 3, 5. Notice that 1 stays after 3 and before 2, exactly as in the input — relative order within each sign is preserved because each cursor writes in read order. The result also starts with a positive, which the problem requires.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
59 / DRILL UNIT 08 · Rearrange by Sign · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

The harder variant drops the guarantee that positives and negatives are equally numerous. Why does this approach break, and what replaces it?

The stepping-by-two scheme assumes n/2 of each. With three positives and one negative in an array of four, pos would want slots 0, 2, 4 — and 4 is out of bounds. The fix is to stop being clever: collect the two groups, alternate while both are non-empty, then append whatever remains in its original order. It is O(n) either way. Knowing which guarantee a neat trick depends on is what stops you reaching for it when the guarantee is gone.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
60 / MECHANISM UNIT 08 · SIGNSPLIT · CODE MIRRORED

THE WRITE POINTER, DOUBLED

Two destination cursors, one at 0 and one at 1, each stepping by two. Positives land on even slots and negatives on odd ones, and because the cursors move in twos they can never collide — no bounds check needed, no interleaving logic. Relative order survives for free, because values are written in exactly the order they were read.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
61 / PROBLEM #12 · WRITE-POINTER · MED

Rearrange Array Elements by Sign

MED write-pointer ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Alternating signs, starting with positive” and “preserve the relative order within positives and within negatives”. The ordering clause is the whole problem — without it you could sort or partition freely. The easy variant also guarantees equal counts of each sign, and that guarantee is what licenses the neat two-cursor trick.

INTUITION

Allocate the result and keep two destination cursors: one at index 0 for positives, one at index 1 for negatives. Walk the input once, and send each value to its own cursor, advancing that cursor by two. Because one cursor only ever visits even indices and the other only odd ones, they cannot collide — no bounds checking, no interleaving logic. Writing in read order is what preserves relative order within each sign, for free.

STEPS
  1. Allocate a result array of the same length
  2. Set pos = 0 and neg = 1 — the first even and first odd slot
  3. Walk the input once in its original order
  4. A positive goes to out[pos], then pos += 2
  5. A negative goes to out[neg], then neg += 2
  6. Return the result; both cursors land exactly at the end because the counts are equal
BRUTEO(n) time · O(n) space, two lists
OPTIMALO(n)
↕ SCROLL
// Two destination cursors, each stepping by TWO. One visits only even
// indices and the other only odd ones, so they are disjoint by construction
// and no bounds check between them is ever needed.
vector<int> rearrangeArray(vector<int>& a) {
    vector<int> out(a.size());
    int pos = 0, neg = 1;                   // first even slot, first odd slot

    for (int v : a) {
        if (v > 0) { out[pos] = v; pos += 2; }
        else       { out[neg] = v; neg += 2; }
    }
    return out;                             // order kept within each sign
}
TIMEO(n)one pass, one write per element
SPACEO(n)the result array — the problem returns a new arrangement, so this is inherent
TRAP

Reusing this when the counts are not equal. The stepping-by-two scheme silently assumes exactly n/2 of each sign; with three positives and one negative, pos reaches an index past the end. LeetCode 2149 guarantees the balance, so the code is correct here — but the follow-up variant removes the guarantee, and then you must alternate only while both groups remain and append the leftovers in order. Know which guarantee your trick is standing on, or you will reach for it when it is gone.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #12 · REARRANGE ARRAY ELEMENTS BY SIGN

Rearrange Array Elements by Sign

The walkthrough for #12 Rearrange Array Elements by Sign. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Rearrange Array Elements by Sign
RUNTIME 21:37
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
62 / INTRO UNIT 09 · Next Permutation

UNIT 09 — Next Permutation

The one genuinely non-obvious construction in this deck, and it stops being a trick the moment you hear the argument. A suffix that is already descending is the largest arrangement of its own values — nothing inside it can be improved. So the change has to happen at the last position that still has something bigger to its right. Swap in the smallest value that beats it, then make everything after it as small as possible.

THE QUESTION THIS LECTURE ANSWERS

WHAT IS THE VERY NEXT ARRANGEMENT IN DICTIONARY ORDER, AND HOW DO YOU GET THERE?

LEXICOGRAPHICPIVOTDESCENDING SUFFIXSMALLEST INCREASEIN PLACE
WHAT TO WATCH FOR
  • 01THE PIVOT IS FOUND SCANNING FROM THE RIGHT — THE FIRST a[i] < a[i+1]
  • 02THE SUFFIX AFTER THE PIVOT IS DESCENDING, WHICH IS WHY REVERSING IT SORTS IT
  • 03THE SWAP PARTNER IS THE RIGHTMOST VALUE ABOVE THE PIVOT, AND RIGHTMOST MEANS SMALLEST HERE
  • 04NO PIVOT AT ALL MEANS THE WHOLE ARRAY DESCENDS — REVERSE IT AND YOU HAVE THE FIRST PERMUTATION
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
63 / VIDEO UNIT 09 · Next Permutation

Next Permutation - Intuition in Detail

STRIVER A2Z
Next Permutation
RUNTIME 28:15
AFTER THIS → 3 DRILLS · PROBLEM #13
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
64 / DRILL UNIT 09 · Next Permutation · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why is the final step a reverse of the suffix rather than a sort of it?

Reversing a descending sequence sorts it. The pivot search stopped precisely because everything to its right was non-increasing, and the swap cannot break that property — the incoming value is smaller than the one it replaced but still larger than everything after it. So the suffix is still descending, and one reversal makes it ascending, which is the smallest arrangement. You get an O(n) step where a sort would have cost O(n log n), purely because you knew something about the data.

DRILL 02 · TRACE

Run it on [1, 3, 5, 4, 2]. What is the pivot, what does it swap with, and what is the final answer?

Pivot 3 at index 1. Scanning right to left: 4 ≥ 2 keeps going, 5 ≥ 4 keeps going, 3 < 5 stops — so i = 1. The suffix [5, 4, 2] is descending, confirming it is maximal. The rightmost value above 3 is 4 at index 3, so swap to get [1, 4, 5, 3, 2], then reverse the suffix to get [1, 4, 2, 3, 5]. The MECHANISM slide walks exactly this and asks you to pick the swap partner.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
65 / DRILL UNIT 09 · Next Permutation · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This picks the swap partner by scanning from the LEFT of the suffix instead of the right. What does it produce?

int j = i + 1;
while (j < n && a[j] > a[i]) j++;  // first value above the pivot
swap(a[i], a[j - 1]);

It overshoots. The suffix is descending, so scanning from the left finds the largest value exceeding the pivot, while you want the smallest one — the minimum possible increase. On [1, 3, 5, 4, 2] it would swap 3 with 5, giving [1, 5, 2, 3, 4], which is a later permutation than [1, 4, 2, 3, 5]. Still a valid permutation, still larger, still wrong. “Next” means smallest step up, and on a descending suffix that is the rightmost qualifying value.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
66 / MECHANISM UNIT 09 · NEXTPERM · CODE MIRRORED

THE SUFFIX THAT IS ALREADY MAXIMAL TELLS YOU WHERE TO CHANGE

The only non-obvious construction here, and it stops being a trick once you see the argument. A descending suffix is already the largest arrangement of its own values, so nothing inside it can be improved — the change must happen at the last position that still has something bigger to its right. Swap in the smallest such value, then make the suffix as small as possible by reversing it.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
67 / PROBLEM #13 · CONSTRUCT · MED

Next Permutation

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

Next permutation in lexicographic order” and “in place, constant extra memory”. “Next” is the operative word: you are not generating permutations or sorting, you are making the smallest possible increase. That phrasing always points at a local, constructive rule rather than a search — and the O(1) space clause rules out generating anything.

INTUITION

Scan from the right for the first position whose value is smaller than its neighbour — the pivot. Everything after it is descending, which means that suffix is already the largest arrangement of its own values and nothing inside it can improve. So the pivot is the last place with room to grow. Swap it with the smallest value to its right that still beats it (on a descending suffix, that is the rightmost such value), then reverse the suffix to make it as small as possible.

STEPS
  1. Scan i from n−2 leftwards while a[i] >= a[i+1] — stop at the pivot
  2. If no pivot exists the array is fully descending: reverse it and return
  3. Scan j from n−1 leftwards for the first value greater than a[i]
  4. Swap a[i] and a[j] — the smallest possible increase at the pivot
  5. The suffix is still descending, so reverse it to make it ascending and minimal
  6. Reversing rather than sorting is O(n) and correct because of that guarantee
BRUTEO(n! · n)
OPTIMALO(n)
↕ SCROLL
// A descending suffix is ALREADY the largest arrangement of its values,
// so the change must happen at the last position with room to grow.
void nextPermutation(vector<int>& a) {
    int n = a.size(), i = n - 2;
    while (i >= 0 && a[i] >= a[i + 1]) i--;         // find the pivot

    if (i >= 0) {
        int j = n - 1;
        while (a[j] <= a[i]) j--;                   // RIGHTMOST value above it
        swap(a[i], a[j]);                           // smallest possible increase
    }
    reverse(a.begin() + i + 1, a.end());            // descending -> ascending
}
TIMEO(n)at most three linear scans over the array
SPACEO(1)a handful of indices; every move is a swap
TRAP

Scanning for the swap partner from the left of the suffix finds the largest value above the pivot instead of the smallest, producing a permutation that is larger than the input but not the next one — valid-looking and wrong. The second trap is forgetting the no-pivot case: on a fully descending array i ends at −1, and reverse(a.begin() + 0, a.end()) correctly yields the first permutation. Writing i + 1 without checking i >= 0 around the swap is what breaks it.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #13 · NEXT PERMUTATION

Next Permutation - Intuition in Detail

The walkthrough for #13 Next Permutation. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Next Permutation - Intuition in Detail
RUNTIME 28:15
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
68 / INTRO UNIT 10 · Longest Consecutive Run

UNIT 10 — Longest Consecutive Run

A hash set, and one guard clause that turns a nested loop into a linear algorithm. Put every value in a set, then walk runs — but only start walking from a value with no left neighbour. That single check means each run is traversed exactly once across the whole execution, so the inner while does O(n) total work no matter how it looks. This is also the unit where “consecutive” and “contiguous” get separated for good.

THE QUESTION THIS LECTURE ANSWERS

HOW IS A LOOP INSIDE A LOOP STILL O(n)?

HASH SETRUN HEADAMORTISEDCONSECUTIVE vs CONTIGUOUSO(n)
WHAT TO WATCH FOR
  • 01THE GUARD IS THE ALGORITHM — WITHOUT IT THE SAME CODE IS O(n²)
  • 02CONSECUTIVE MEANS CONSECUTIVE IN VALUE, NOT ADJACENT IN THE ARRAY
  • 03THE SET DEDUPLICATES FOR FREE, WHICH MATTERS ON INPUTS WITH REPEATS
  • 04SORTING IS THE HONEST O(n log n) FALLBACK AND IS WORTH SAYING OUT LOUD
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
69 / VIDEO UNIT 10 · Longest Consecutive Run

Longest Consecutive Sequence

STRIVER A2Z
Longest Consecutive Run
RUNTIME 23:11
AFTER THIS → 3 DRILLS · PROBLEM #14
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
70 / DRILL UNIT 10 · Longest Consecutive Run · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

The code has a while nested inside a for, yet it is O(n). What makes that true?

Each run is walked exactly once, ever. The guard if (s.count(v-1)) continue; means only a run's head does any walking, and a run of length k is walked from its head for k steps and never touched again. Summing over all runs gives at most n total steps in the inner loop across the entire execution. O(1) lookup is necessary but not sufficient — remove the guard and every element walks its whole run, which is genuinely O(n²). The MECHANISM slide counts the walk steps so you can watch them stay under n.

DRILL 02 · TRACE

On [100, 4, 200, 1, 3, 2], which values actually start a walk?

100, 200 and 1. For 4, the value 3 is present, so it is skipped instantly; likewise 3 (2 present) and 2 (1 present). 100 walks a run of length 1, 200 the same, and 1 walks 1→2→3→4 for a length of 4, which is the answer. Total inner-loop work: 6 steps for 6 elements. Notice the three skipped values cost one set lookup each and nothing more — that is the guard paying for itself.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
71 / DRILL UNIT 10 · Longest Consecutive Run · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

A candidate says “longest run” and reaches for Kadane. Why is that the wrong pattern here?

Two different words. Contiguous means adjacent in the array — that is Kadane's world, and position matters. Consecutive here means adjacent in value, and the array's ordering is irrelevant: [100, 4, 200, 1, 3, 2] has its answer scattered across non-adjacent positions. Mistaking one for the other sends you down a pattern that cannot represent the question. This is the single most valuable habit in the deck — read the statement's nouns precisely, because they select the pattern.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
72 / MECHANISM UNIT 10 · RUNS · CODE MIRRORED

A LOOP INSIDE A LOOP, AND STILL O(n)

This widget exists to make one claim believable. There is a while nested inside a for, and the whole thing is linear — because of the guard: only a value with no left neighbour starts a walk, so every run is walked exactly once in its entire lifetime. Watch the TOTAL STEPS WALKED counter against the array length; it never exceeds it. Remove the guard and the same code is O(n²).

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
73 / PROBLEM #14 · HASHING · MED

Longest Consecutive Sequence

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

Consecutive sequence” — consecutive in value, not contiguous in position, so the array's order carries no information and you are free to hash it. Add the explicit O(n) requirement and sorting is ruled out too, which leaves a set. The word to notice is the one that is absent: nothing says subarray, so nothing ties the answer to adjacency.

INTUITION

Put every value in a hash set so membership is O(1). Then, for each value, ask whether it is the head of a run by checking if v−1 is absent. Only heads walk forward counting v+1, v+2, and so on. That guard is what keeps the nested loop linear: a run of length k is walked exactly once in the algorithm's entire lifetime, from its own head, so the total inner work across all runs is at most n.

STEPS
  1. Insert every value into a hash set — this also removes duplicates for free
  2. Iterate over the values
  3. Skip immediately if v−1 is in the set: something else is the head of this run
  4. Otherwise walk v+1, v+2, … while each is present, counting the length
  5. Track the maximum length seen
  6. Total walking is bounded by n, because no run is ever walked twice
BRUTEO(n log n)
OPTIMALO(n)
↕ SCROLL
// A while inside a for, and still O(n) -- because of ONE guard.
// Only a value with no left neighbour starts a walk, so every run is
// traversed exactly once across the whole execution. Drop the guard and
// the identical code becomes O(n^2).
int longestConsecutive(vector<int>& a) {
    unordered_set<int> s(a.begin(), a.end());       // dedupes for free
    int best = 0;

    for (int v : s) {
        if (s.count(v - 1)) continue;               // not a head - skip
        int x = v, len = 1;
        while (s.count(x + 1)) { x++; len++; }      // walk the run once
        best = max(best, len);
    }
    return best;
}
TIMEO(n)each run walked exactly once; the guard is what makes this linear
SPACEO(n)the hash set holds every distinct value
TRAP

Dropping the v−1 guard leaves code that returns the right answer on every small test and is genuinely O(n²) — on a single run of 10⁵ consecutive values it walks the entire run from every element and times out. The judge reports TLE, not a wrong answer, so it reads as “my solution is too slow” rather than “I omitted the line that makes it fast”. The second trap is reaching for Kadane because the word “longest” appears: Kadane needs contiguity, and this problem deliberately has none.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #14 · LONGEST CONSECUTIVE SEQUENCE

Longest Consecutive Sequence

The walkthrough for #14 Longest Consecutive Sequence. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Longest Consecutive Sequence
RUNTIME 23:11
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
74 / INTRO UNIT 11 · The Matrix as Its Own Flag

UNIT 11 — The Matrix as Its Own Flag

The obvious solution needs a copy of the whole matrix, or two extra arrays. The O(1)-space answer is a small idea with one sharp corner: use the matrix's own first row and first column as the notebook. When a cell is zero, mark the top of its column and the start of its row. The one collision — a[0][0] would have to record both row 0 and column 0 — is handled by lifting column 0 out into a single boolean.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU REMEMBER WHICH ROWS AND COLUMNS TO ZERO WITHOUT ANY EXTRA MEMORY?

IN-PLACE MARKERFIRST ROW/COLTHE a[0][0] COLLISIONTWO PASSESO(1) SPACE
WHAT TO WATCH FOR
  • 01ROW 0 AND COLUMN 0 STOP BEING DATA AND BECOME THE MARKER STRIP
  • 02a[0][0] IS THE ONE COLLISION — IT CANNOT MARK BOTH, SO col0 GETS ITS OWN BOOLEAN
  • 03PASS TWO RUNS BOTTOM-UP AND RIGHT-TO-LEFT SO THE MARKERS ARE READ BEFORE THEY ARE OVERWRITTEN
  • 04MARKING RATHER THAN CLEARING IS WHAT STOPS A FRESH ZERO BEING READ AS AN ORIGINAL ONE
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
75 / VIDEO UNIT 11 · The Matrix as Its Own Flag

Set Matrix Zeroes - O(1) Space Approach

STRIVER A2Z
The Matrix as Its Own Flag
RUNTIME 30:07
AFTER THIS → 3 DRILLS · PROBLEM #15
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
76 / DRILL UNIT 11 · The Matrix as Its Own Flag · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does column 0 need a separate boolean when row 0 does not get one?

a[0][0] is the shared corner. It belongs to both the row-marker strip (column 0) and the column-marker strip (row 0), so it can only carry one of the two bits. The convention is to let a[0][0] mean “row 0 contains a zero” and to lift the other question — “does column 0 contain a zero?” — into a standalone boolean. It is not an optimisation; without it one of the two strips is silently corrupted. This single collision is the entire subtlety of the problem, and it is exactly what an interviewer is probing for.

DRILL 02 · TRACE

Why must the second pass iterate from the bottom-right corner backwards, rather than top-left forwards?

The markers live in the cells you would overwrite first. If you zeroed the interior top-down and a[0][c] happened to be a marker, you would clear it before the rows below had a chance to consult it. Walking bottom-up and right-to-left visits every interior cell before touching the first row and column, so the notes survive exactly as long as they are needed. Then column 0 is handled last, from the col0 boolean. Order of traversal is load-bearing here — it is not a style choice.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
77 / DRILL UNIT 11 · The Matrix as Its Own Flag · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This marks correctly but zeroes the matrix in the FIRST pass, as it finds each zero. What goes wrong?

for (r) for (c)
    if (a[r][c] == 0) {
        for (k) a[r][k] = a[k][c] = 0;  // zero it now
    }

A cascade. The moment you zero a row in pass one, every zero you just wrote looks identical to an original zero to the rest of the scan — so their columns get zeroed, then those columns' rows, and the whole matrix collapses to zeros on almost any input. This is the reason for the two-pass structure: separate deciding from doing. Pass one only marks; pass two only acts. Conflating them is the single most common way this problem is failed, and it fails loudly — an all-zero matrix — which at least announces itself.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
78 / MECHANISM UNIT 11 · SETZERO · CODE MIRRORED

THE MATRIX STORES ITS OWN BOOKKEEPING

The obvious solution needs two arrays; the O(1) answer uses the matrix's own first row and column as marker strips. A zero at (r, c) marks a[r][0] and a[0][c] — it leaves a note, it does not clear anything yet. The one collision is a[0][0], which cannot mark both row 0 and column 0, so column 0 gets a single boolean. Watch pass two run bottom-up: the markers live in cells it must read before it overwrites them.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
79 / PROBLEM #15 · INDEX-ALGEBRA · MED

Set Matrix Zeroes

MED index-algebra ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

In place, with constant extra space” on a matrix problem is the whole signal. The naive answer — record which rows and columns to zero in two auxiliary arrays — is O(m+n) space and obvious; the follow-up explicitly asks you to beat it. The moment you are told “no extra memory”, the matrix itself has to become the scratchpad.

INTUITION

Use the first row and first column as marker strips. In pass one, whenever a cell is zero, write a zero to the top of its column and the start of its row — you are leaving notes, not clearing data yet. The single collision is a[0][0], which cannot mark both row 0 and column 0, so column 0's fate is tracked in one separate boolean. In pass two, walk the interior from the bottom-right backwards, zeroing any cell whose row or column marker is set, and handle column 0 last.

STEPS
  1. Scan the matrix; for each zero, set a[r][0] = 0 and either a[0][c] = 0 or col0 = true
  2. a[0][0] can only hold one mark, so column 0 uses the standalone col0 boolean
  3. Second pass runs from the bottom-right corner backwards toward the top-left
  4. Zero any interior cell whose row-marker a[r][0] or column-marker a[0][c] is zero
  5. Handle column 0 last, from col0, so its markers survive until read
  6. The markers live in cells read last, which is why traversal order matters
BRUTEO(m·n) time · O(m+n) space
OPTIMALO(m·n)
↕ SCROLL
// The matrix stores its own bookkeeping: row 0 and column 0 become the
// marker strips. a[0][0] is the ONE collision -- it cannot mark both, so
// column 0's fate lives in a single boolean.
void setZeroes(vector<vector<int>>& a) {
    int m = a.size(), n = a[0].size();
    bool col0 = false;

    for (int r = 0; r < m; r++) {
        if (a[r][0] == 0) col0 = true;
        for (int c = 1; c < n; c++)
            if (a[r][c] == 0) a[r][0] = a[0][c] = 0;    // leave notes, do not clear
    }
    for (int r = m - 1; r >= 0; r--) {                  // bottom-up: read before overwrite
        for (int c = n - 1; c >= 1; c--)
            if (a[r][0] == 0 || a[0][c] == 0) a[r][c] = 0;
        if (col0) a[r][0] = 0;                          // column 0 handled last
    }
}
TIMEO(m·n)two passes over the matrix
SPACEO(1)one boolean beyond the matrix itself — the first row and column ARE the storage
TRAP

Zeroing during the first pass. The instant you clear a real row while still scanning, the fresh zeros you wrote are indistinguishable from original ones, and the next iterations wipe their rows and columns too — a cascade that collapses almost any matrix to all zeros. The fix is the two-pass split: pass one only marks, pass two only acts. The second, quieter trap is iterating pass two top-down, which overwrites the first-row markers before the rows beneath have read them.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #15 · SET MATRIX ZEROES

Set Matrix Zeroes - O(1) Space Approach

The walkthrough for #15 Set Matrix Zeroes. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Set Matrix Zeroes - O(1) Space Approach
RUNTIME 30:07
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
80 / INTRO UNIT 12 · Transpose & Reverse

UNIT 12 — Transpose & Reverse

Two operations, each trivially correct, whose composition is a 90° rotation — and seeing why is far better than memorising it. Transpose reflects the matrix across its main diagonal, turning rows into columns. Reverse each row then flips left-to-right. Do both and every element lands exactly where a clockwise turn would put it, with no scratch matrix and no index gymnastics.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU ROTATE A MATRIX 90° WITHOUT ALLOCATING A SECOND ONE?

TRANSPOSEMAIN DIAGONALREFLECTIONCOMPOSITIONIN PLACE
WHAT TO WATCH FOR
  • 01THE INNER LOOP STARTS AT c = r+1, NOT 0 — OTHERWISE EVERY PAIR SWAPS TWICE AND NOTHING MOVES
  • 02THE MAIN DIAGONAL NEVER MOVES DURING THE TRANSPOSE
  • 03TRANSPOSE-THEN-REVERSE-ROWS IS CLOCKWISE; REVERSE-ROWS-THEN-TRANSPOSE IS ANTICLOCKWISE
  • 04BOTH STEPS ARE O(1) SPACE, SO THE WHOLE THING IS IN PLACE
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
81 / VIDEO UNIT 12 · Transpose & Reverse

Rotate Matrix by 90 Degrees

STRIVER A2Z
Transpose & Reverse
RUNTIME 17:47
AFTER THIS → 3 DRILLS · PROBLEM #16
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
82 / DRILL UNIT 12 · Transpose & Reverse · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does the transpose loop start its inner index at c = r + 1 instead of c = 0?

Swapping twice is identity. A transpose swaps a[r][c] with a[c][r]. If the inner loop ran the full row, it would later reach position (c, r) and swap them back — every pair touched twice, the matrix unchanged. Starting at c = r + 1 visits each off-diagonal pair exactly once and leaves the diagonal (where r = c) untouched, which is correct because a diagonal element maps to itself. This is the classic in-place-swap off-by-one, and it produces a silently unchanged matrix rather than a crash.

DRILL 02 · TRACE

After transposing [[1,2,3],[4,5,6],[7,8,9]], what does row 0 look like, and what is it after the row-reversal step?

[1,4,7], then reversed to [7,4,1]. The transpose turns the first column (1,4,7) into the first row, so row 0 becomes [1,4,7]. That is the correct set of values for the rotated top row, but in the wrong order — a clockwise turn should read [7,4,1]. Reversing the row fixes it. Watch the MECHANISM slide do exactly this: the transpose gathers the right values, the reversal orients them.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
83 / DRILL UNIT 12 · Transpose & Reverse · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You need to rotate 90° anticlockwise instead. What is the minimal change?

Swap the order of the two operations. Clockwise is transpose-then-reverse-rows; anticlockwise is reverse-rows-then-transpose (equivalently, transpose then reverse each column). Both are still two O(1)-space passes. Understanding rotation as a composition of a reflection and a reflection is what lets you derive any of the four orientations on the spot, instead of memorising four separate index formulas — which is the whole reason this unit teaches the why rather than the code.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
84 / MECHANISM UNIT 12 · ROTATE · CODE MIRRORED

TRANSPOSE, THEN REVERSE EACH ROW

A 90° clockwise rotation is two reflections in sequence. Transpose swaps a[r][c] with a[c][r], reflecting across the main diagonal; reverse each row then flips left-to-right. The inner loop starts at c = r+1 for a reason worth watching — running the full square swaps every pair twice and leaves the matrix exactly as it began. The diagonal never moves.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
85 / PROBLEM #16 · INDEX-ALGEBRA · MED

Rotate Image

MED index-algebra ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Rotate the image in place” on a square matrix. In place plus square is the tell that this is an index-algebra problem with a two-step decomposition, not something that needs a fresh grid. Nothing is compared and nothing is searched — every element's destination is a pure function of its coordinates.

INTUITION

A 90° clockwise rotation is exactly two reflections done in sequence. First transpose the matrix — swap a[r][c] with a[c][r] — which reflects it across the main diagonal and turns columns into rows. Then reverse each row, which flips it left-to-right. The composition sends every element precisely where a clockwise turn would, and both steps are in place, so no second matrix is ever allocated.

STEPS
  1. Transpose: for each r, swap a[r][c] with a[c][r] for all c > r
  2. Start the inner loop at c = r+1 so each pair is swapped once, not twice
  3. The main diagonal maps to itself and is left untouched
  4. Reverse each row to flip the transposed matrix left-to-right
  5. The result is the input rotated 90° clockwise, computed in place
  6. For anticlockwise, reverse the rows first and then transpose
BRUTEO(n²) time · O(n²) space, with a copy
OPTIMALO(n²)
↕ SCROLL
// 90 clockwise = transpose, then reverse each row. Both are reflections,
// and their composition is the rotation. The inner loop starts at r+1 so
// each pair swaps ONCE -- running the full square would undo the transpose.
void rotate(vector<vector<int>>& a) {
    int n = a.size();

    for (int r = 0; r < n; r++)
        for (int c = r + 1; c < n; c++)
            swap(a[r][c], a[c][r]);         // reflect across the main diagonal
    for (auto& row : a)
        reverse(row.begin(), row.end());    // flip left-to-right
}
TIMEO(n²)each element is touched a constant number of times
SPACEO(1)swaps in place; no auxiliary matrix
TRAP

Starting the transpose's inner loop at 0. Running the full square swaps every off-diagonal pair twice, and two swaps are the identity — so the matrix comes back unchanged after the transpose, and the row-reversal then produces a horizontal mirror instead of a rotation. It does not crash and it looks like a plausible transformation, just the wrong one. Start the inner index at r + 1. The related slip is forgetting the problem is square-only; on a non-square matrix the in-place transpose is not even defined.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #16 · ROTATE IMAGE

Rotate Matrix by 90 Degrees

The walkthrough for #16 Rotate Image. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Rotate Matrix by 90 Degrees
RUNTIME 17:47
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
86 / INTRO UNIT 13 · Four Shrinking Bounds

UNIT 13 — Four Shrinking Bounds

No cleverness, just four bounds and the discipline to shrink them. Walk the top row left-to-right, the right column top-to-bottom, the bottom row right-to-left, the left column bottom-to-top — then tighten each bound inward and repeat. The only place it goes wrong is the last layer, where a lone remaining row or column can be emitted twice; two guard conditions prevent exactly that.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU WALK A MATRIX IN A SPIRAL WITHOUT EVER REVISITING A CELL?

FOUR BOUNDSLAYERSHRINK INWARDTHE DOUBLE-EMIT GUARDBOUNDARY
WHAT TO WATCH FOR
  • 01FOUR BOUNDS — top, bottom, left, right — AND EACH SHRINKS AFTER ITS EDGE IS WALKED
  • 02THE GUARD top <= bot BEFORE THE BOTTOM ROW STOPS A SINGLE ROW BEING EMITTED TWICE
  • 03THE GUARD left <= right BEFORE THE LEFT COLUMN DOES THE SAME FOR A LONE COLUMN
  • 04THE LOOP CONDITION AND THE TWO INNER GUARDS ARE NOT THE SAME CHECK
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
87 / VIDEO UNIT 13 · Four Shrinking Bounds

Spiral Traversal of a Matrix

STRIVER A2Z
Four Shrinking Bounds
RUNTIME 16:33
AFTER THIS → 3 DRILLS · PROBLEM #17
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
88 / DRILL UNIT 13 · Four Shrinking Bounds · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

After the top row and right column of a layer are walked, why is a top <= bot check needed before walking the bottom row?

The single-row-left case. After walking the top row you do top++; if only one row remained, top is now greater than bot. The bottom-row loop would then walk that same row backwards and emit every value twice. The guard top <= bot catches it. Crucially this is not the same as the outer while condition — the bounds change inside one iteration, so you must re-check. Same logic guards the left column with left <= right. The guards are the problem; the four walks are bookkeeping.

DRILL 02 · TRACE

Spiralling [[1,2,3,4],[5,6,7,8],[9,10,11,12]], what are the first six values emitted?

1, 2, 3, 4, 8, 12. Top row left-to-right gives 1, 2, 3, 4; then top shrinks and the right column (index 3) top-to-bottom gives 8, then 12. Next comes the bottom row backwards — 11, 10, 9 — then the left column up. Notice the corners: 4 belongs to the top row, and 8 and 12 to the right column, each emitted exactly once. The MECHANISM slide draws the shrinking frame so you can watch the live region close in.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
89 / DRILL UNIT 13 · Four Shrinking Bounds · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This omits the two inner guards, keeping only the outer while. On which matrices does it break?

while (top <= bot && left <= right) {
    // ... top row, right col ...
    for (c = right; c >= left; c--) out.push(a[bot][c]);
    bot--;
    // ... left col, no guard ...
}

Odd dimensions expose it. When the layers close on a single leftover row or column, the outer while is still true (the bounds have not crossed yet), so the bottom-row and left-column loops run on a line that the top-row and right-column loops already covered — duplicating it. A 3×3 or any m×n with an odd dimension shows it; a clean 2×2 or 4×4 may not, which is why it slips through hand tests. The outer condition guards entry to a layer; the inner guards protect the two second-half walks within it.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
90 / MECHANISM UNIT 13 · SPIRAL · CODE MIRRORED

FOUR BOUNDS THAT ONLY EVER SHRINK

No cleverness — four bounds enclosing the unvisited region, each tightened after its edge is walked. The gold frame is that region closing in. The two guards before the bottom row and the left column are the entire difficulty: on the last layer a lone remaining row or column would otherwise be emitted twice, and those checks are not the same as the outer loop condition.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
91 / PROBLEM #17 · INDEX-ALGEBRA · MED

Spiral Matrix

MED index-algebra ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Return all elements in spiral order” is a pure traversal problem — no optimisation, no data structure, just the discipline to visit each cell once. The signal is that there is no signal: when a matrix problem asks you only to walk it in some order, the answer is boundary tracking, and the entire difficulty is the edge cases.

INTUITION

Keep four bounds — top, bottom, left, right — enclosing the not-yet-visited region. Walk the top row left-to-right and drop top; the right column top-to-bottom and drop right; the bottom row right-to-left and drop bottom; the left column bottom-to-top and raise left. Repeat until the bounds cross. The only subtlety is the final layer: after shrinking, a single remaining row or column must not be walked twice, which two guard checks prevent.

STEPS
  1. Set top=0, bottom=m−1, left=0, right=n−1
  2. Walk the top row left→right, then top++
  3. Walk the right column top→bottom, then right--
  4. If top <= bottom, walk the bottom row right→left, then bottom--
  5. If left <= right, walk the left column bottom→top, then left++
  6. Repeat while top <= bottom and left <= right
BRUTEO(m·n) time · O(m·n) visited-set
OPTIMALO(m·n)
↕ SCROLL
// Four bounds that only shrink. The two inner guards are the whole
// problem: without them a lone final row or column is emitted twice.
vector<int> spiralOrder(vector<vector<int>>& a) {
    int top = 0, bot = a.size() - 1, left = 0, right = a[0].size() - 1;
    vector<int> out;

    while (top <= bot && left <= right) {
        for (int c = left; c <= right; c++) out.push_back(a[top][c]);
        top++;
        for (int r = top; r <= bot; r++) out.push_back(a[r][right]);
        right--;
        if (top <= bot) {                            // guard: still a row left?
            for (int c = right; c >= left; c--) out.push_back(a[bot][c]);
            bot--;
        }
        if (left <= right) {                         // guard: still a column left?
            for (int r = bot; r >= top; r--) out.push_back(a[r][left]);
            left++;
        }
    }
    return out;
}
TIMEO(m·n)each cell is emitted exactly once
SPACEO(1)four integer bounds; the output list is not counted as working space
TRAP

Dropping the two inner guards. The outer while guards entry to a whole layer, but inside one iteration the bounds shift, and on an odd dimension the last leftover row or column would be walked a second time by the bottom-row or left-column loop. It is invisible on a clean even×even matrix and shows up the moment a dimension is odd — the classic “works on my 4×4, fails the judge”. The two checks top <= bot and left <= right, placed before the second-half walks, are not the same as the loop condition and cannot be folded into it.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #17 · SPIRAL MATRIX

Spiral Traversal of a Matrix

The walkthrough for #17 Spiral Matrix. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Spiral Traversal of a Matrix
RUNTIME 16:33
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
92 / INTRO UNIT 14 · Prefix Sums + Hashmap

UNIT 14 — Prefix Sums + Hashmap

This is Two Sum wearing a different hat. Instead of asking “have I seen the complement of this value”, you ask “have I seen a prefix sum equal to (current prefix − k)” — because two equal-difference prefixes bracket a subarray summing to k. The one change from Two Sum is that the map stores counts, not indices, because you are counting subarrays rather than locating one. And it must be seeded with {0: 1}.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU COUNT SUBARRAYS SUMMING TO k WHEN A SLIDING WINDOW WILL NOT WORK?

PREFIX SUMTHE {0:1} SEEDCOUNT NOT INDEXWHY NOT A WINDOWO(n)
WHAT TO WATCH FOR
  • 01THE MAP IS SEEDED WITH {0:1} — THE EMPTY PREFIX, OR EVERY SUBARRAY FROM INDEX 0 IS MISSED
  • 02IT STORES COUNTS OF EACH PREFIX, NOT FIRST INDICES, BECAUSE YOU ARE COUNTING
  • 03A SLIDING WINDOW IS INVALID THE MOMENT NEGATIVES APPEAR — THE SUM STOPS BEING MONOTONIC
  • 04CHECK FOR sum-k BEFORE RECORDING sum, EXACTLY AS TWO SUM CHECKS BEFORE STORING
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
93 / VIDEO UNIT 14 · Prefix Sums + Hashmap

Count Subarrays with Sum Equals K

STRIVER A2Z
Prefix Sums + Hashmap
RUNTIME 24:09
AFTER THIS → 3 DRILLS · PROBLEM #18
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
94 / DRILL UNIT 14 · Prefix Sums + Hashmap · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why must the prefix-count map be initialised with {0: 1} before the loop starts?

It accounts for subarrays that start at the beginning. When the running prefix through index i equals k exactly, the subarray a[0..i] is a valid answer — and it is “closed” by the empty prefix of sum 0. If 0 is not already in the map with a count of 1, that whole class of subarrays goes uncounted, and the answer is short by exactly the number of prefixes equal to k. It is invisible on inputs where no prefix hits k, which is how the bug survives casual testing. The seed is not defensive; it is a real occurrence of the empty prefix.

DRILL 02 · TRACE

On [3, 4, 7, 2, -3, 1, 4, 2] with k = 7, how many subarrays sum to 7, and does a sliding window find them all?

Four subarrays: [3,4], [7], [7,2,-3,1] and [1,4,2]. A sliding window relies on the sum growing as the window widens, so that overshooting k means shrinking from the left. The -3 breaks that: the sum is no longer monotonic, so a window can skip right past valid subarrays or shrink when it should not. The prefix-sum-plus-map approach does not care about monotonicity at all, which is exactly why it is the right tool once negatives are on the table. The MECHANISM slide finds all four.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
95 / DRILL UNIT 14 · Prefix Sums + Hashmap · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

This exact template also solves “longest subarray with sum k” and “count subarrays with XOR = k”. What changes between the three?

The prefix operation and what the map stores are the only knobs. For a count the map holds occurrence counts and you accumulate them; for the longest subarray it holds the earliest index of each prefix, so you can measure spans and keep the widest. Swap the running sum for a running XOR and the complement from sum − k to xor ⊕ k, and the identical skeleton counts XOR-subarrays. These are the two lectures this deck deliberately dropped — they are this one problem with two knobs turned, and recognising that is worth more than solving each fresh.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
96 / MECHANISM UNIT 14 · PREFIX · CODE MIRRORED

TWO SUM, WEARING A DIFFERENT HAT

A subarray sums to k exactly when two prefix sums differ by k — so “is there an earlier prefix equal to (current − k)?” is the same O(1) map lookup Two Sum uses for complements. The map stores counts, not indices, because you are counting subarrays, and it is seeded with {0:1} for the empty prefix. Negatives are handled for free, which is precisely why a sliding window cannot be used here.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
97 / PROBLEM #18 · HASHING · MED

Subarray Sum Equals K

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

Count the subarrays” summing to k, and — read the constraints — the values may be negative. Those two facts together are the whole signal. “Count” rather than “longest” means the map stores occurrences; negatives means a sliding window is off the table, because the running sum is no longer monotonic as the window grows.

INTUITION

This is Two Sum on prefix sums. Let P(i) be the sum of the first i elements. A subarray a[j+1..i] sums to k exactly when P(i) − P(j) = k, i.e. P(j) = P(i) − k. So as you sweep and maintain the running prefix, ask how many earlier prefixes equal (current − k); each one closes a distinct valid subarray ending here. A hash map of prefix → count answers that in O(1), and it must be seeded with {0: 1} for the empty prefix.

STEPS
  1. Keep a running prefix sum and a map from prefix value to how many times it occurred
  2. Seed the map with {0: 1} — the empty prefix, which closes subarrays starting at index 0
  3. At each element, add it to the running sum
  4. Add map[sum − k] to the answer: every earlier equal-difference prefix is a hit
  5. Then increment map[sum], recording this prefix for later elements
  6. Check before recording, exactly as Two Sum checks the complement before storing
BRUTEO(n²)
OPTIMALO(n)
↕ SCROLL
// Two Sum on prefix sums: a[j+1..i] sums to k iff P(j) = P(i) - k.
// The map counts prefix OCCURRENCES because we are counting subarrays,
// and it is seeded with {0:1} for the empty prefix. Negatives are handled
// for free -- which is exactly why a sliding window will not do.
int subarraySum(vector<int>& a, int k) {
    unordered_map<int, int> cnt{{0, 1}};    // the empty prefix counts
    int sum = 0, total = 0;

    for (int v : a) {
        sum += v;
        total += cnt[sum - k];              // every earlier P(j)=sum-k closes one
        cnt[sum]++;                         // record AFTER checking
    }
    return total;
}
TIMEO(n)one pass with O(1) expected map operations
SPACEO(n)the map holds at most n distinct prefix sums
TRAP

Forgetting the {0: 1} seed silently undercounts by exactly the number of prefixes that equal k — every subarray starting at index 0 is missed. It is invisible whenever no prefix happens to hit k, so it survives casual testing and fails specific cases. The larger trap is reaching for a sliding window because the phrase “subarray sum” feels window-shaped: with negative numbers the sum is not monotonic in the window width, so shrinking on overshoot is unsound and the window misses answers. If the array were guaranteed non-negative, a window would be valid and cheaper — reading that constraint is what tells you which tool applies.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
SOLUTION #18 · SUBARRAY SUM EQUALS K

Count Subarrays with Sum Equals K

The walkthrough for #18 Subarray Sum Equals K. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Count Subarrays with Sum Equals K
RUNTIME 24:09
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
98 / RECALL RETRIEVAL, NOT RECOGNITION

PICK THE PATTERN FROM THE STATEMENT

DRILL 01 · TRANSFER

“Given an unsorted array, return the length of the longest run of consecutive integers.” n ≤ 10⁵. Which pattern, and why not the obvious one?

Hash set with a left-neighbour guard. Sorting works and is the honest fallback, but the O(n) answer is the point: put everything in a set, then for each value only begin counting if v-1 is absent — that makes each run walked exactly once, so the total work is O(n) despite the nested loop. Kadane is wrong here because the run is consecutive in value, not contiguous in position. “Consecutive” and “contiguous” are different words and they select different patterns.

DRILL 02 · RECALL

You need to COUNT subarrays summing to exactly k, not find the longest. What changes?

Store counts, and accumulate. For the longest subarray you store the earliest index a prefix was seen, because earliest gives the longest span. For a count you store how many times that prefix has occurred and add all of them, since every earlier occurrence closes a distinct valid subarray. And note a sliding window is simply invalid once negatives are allowed — the sum is no longer monotonic as the window grows, so shrinking on overshoot loses answers.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
99 / TRAPS PLAUSIBLE, WRONG, AND SILENT

SIX WAYS TO BE PLAUSIBLY WRONG

Every one of these compiles, runs, and returns a number that looks reasonable. None of them crashes. That is the entry test for this slide — a crash teaches itself; a wrong answer costs you an hour.

THE UNVERIFIED VOTE

Boyer–Moore's survivor is only the only possible majority element, never a proof that one exists. Skip the second counting pass and [1,2,3] confidently returns 3. LeetCode 169 guarantees a majority so it passes; 169's twin 229 does not, and the same habit fails there.

k %= n, OR ROTATION EXPLODES

Rotating by k where k > n is legal input on LeetCode 189. Without the modulo, a.begin() + k runs past the end — and because it is undefined behaviour it often does not crash, it just produces a plausible wrong array on the judge's larger tests and passes yours.

SORTING WHEN ORDER IS THE ANSWER

Move Zeroes and Rearrange by Sign both say “preserve relative order”. A partition that swaps from the back is O(n) and correct-looking, and it silently reverses the order of the values it moves. The tests that catch it are not the small ones.

PREFIX SUMS AND THE MAP THAT FORGOT ZERO

Counting subarrays with sum k needs mp[0] = 1 seeded before the loop, or every subarray that starts at index 0 goes uncounted. The answer is off by exactly the number of valid prefixes — small, plausible, and invisible on [1,2,3].

INTEGER OVERFLOW IN 4SUM AND REVERSE PAIRS

Four ints near 10⁹ sum past 2³¹ and wrap to a negative. The comparison then succeeds against a target it should never have matched. Accumulate in long long; in Reverse Pairs, compare a[i] > 2LL * a[j] and not a[i]/2 > a[j], which truncates.

SKIPPING DUPLICATES ON THE WRONG SIDE

In 3Sum you must skip duplicates for the fixed index and for both pointers after a hit. Skipping only the fixed one yields duplicate triplets; skipping before recording the hit loses valid ones. Both mistakes produce a list that looks right until the judge diffs it.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
100 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

Nine patterns, eighteen problems. This is the slide to reread the night before — the right-hand column is the part that actually matters, because recognising the pattern is the whole difficulty.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
Write pointer (r reads, w commits)
O(n)
O(1)
the answer is the input compacted, rearranged, or its new length
XOR fold
O(n)
O(1)
everything is paired except the answer — no sort, no map, order-free
Sum identity n(n+1)/2
O(n)
O(1)
values are a permutation of a known range and one or two are wrong
Hashmap complement
O(n)
O(n)
you need the partner of the current value and the array is unsorted
Dutch national flag
O(n)
O(1)
exactly three distinct values, one pass, no counting
Boyer–Moore voting
O(n)
O(1)
“more than n/k times” — then ALWAYS verify
Kadane
O(n)
O(1)
maximum over contiguous subarrays; ask does extending beat restarting
Prefix sum + hashmap
O(n)
O(n)
COUNT subarrays hitting an exact sum — seed the map with {0:1}
Sort + shrinking pointers
O(n^(k−1))
O(1)
k values summing to a target, order irrelevant, duplicates skipped
Sort by start, sweep
O(n log n)
O(1)
intervals — overlap can only be with the one you just kept
Count inside merge sort
O(n log n)
O(n)
count pairs across a split that violate an order relation
Transpose + reverse
O(n²)
O(1)
rotate a square matrix in place, no auxiliary grid
INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
101 / CLOSE STEP 03 · DECK 1 OF 2

EIGHTEEN DOWN

These nine patterns are the ones every other topic borrows — the two-pointer walk reappears in strings, the prefix map in subarrays and DP, the running best in stocks. Deck 2 stacks them into the eight Hard rows.

00%
OF THIS DECK SOLVED
← ALL TOPICSDECK 2 · HARD →STEP 02 · SORTING

Lectures are Striver's A2Z DSA course. Problem links are LeetCode. 14 units from 14 of the playlist's 28 lectures; the Hard rows' 8 lectures are deck 2.

INVARIANT · ARRAYS · ONE PASS · TWO INDICES · DECK 1 OF 2
■ TOO SMALL TO READ
1280×720
INVARIANT · STEP 03 · 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.