Short problems with techniques that transfer: maximum subarray, majority and heavy hitters, circular arrays and rotations, and in-place permutation tricks.
Maximum subarray (Kadane)
cur is the best sum of a subarray ending at : extend it or start fresh.
ll best = LLONG_MIN, cur = 0;
for (ll x : a) { cur = max(x, cur + x); best = max(best, cur); } // non-empty subarrayProof: Kadane's algorithm is correct
Let be the true maximum sum of a (non-empty) subarray ending exactly at . Claim
curequals after processing : any subarray ending at either is alone, or extends some subarray ending at by appending , so — exactly the updatecur = max(x, cur + x)withcurholding by induction (base case , matchingcurafter the first element since the running sum starts at 0). Sincebesttracks and every subarray ends at some ,bestconverges to the true maximum over all non-empty subarrays.
The prefix-sum view generalises better:
| Variant | Method |
|---|---|
| empty subarray allowed | start best = 0 |
| length | keep |
| length in | sliding window minimum of prefixes |
| sum , maximise | set of prefixes, lower_bound(p_r - K) |
| max product | track both max and min ending here (negatives swap them); zeros reset |
| circular | ; if all are negative, the second term is the empty array, so use plain Kadane |
| disjoint subarrays | DP with states (used , inside or not); for large use the Aliens trick, since the answer is concave in |
| 2D max submatrix | fix a pair of rows, collapse columns, Kadane: |
| delete at most one element | DP with states “deletion used / not used” |
| with point updates, range queries | segment tree node |
| stock buy-sell, max profit, one transaction | Kadane on consecutive price differences |
struct Node { ll sum, pre, suf, best; };
Node merge(Node a, Node b) {
return {a.sum + b.sum, max(a.pre, a.sum + b.pre), max(b.suf, b.sum + a.suf),
max({a.best, b.best, a.suf + b.pre})};
}Proof: the four-value merge is correct
For a range (left part then right part):
sumis additive, trivially correct.pre(best prefix sum of ) is either the best prefix of alone, or all of plus a prefix of — and since any prefix of is at most , the best such combination is ; taking the max of the two cases is exactlymax(a.pre, a.sum + b.pre). Symmetricallysufismax(b.suf, b.sum + a.suf). Forbest(best subarray sum within ): the optimal subarray either lies entirely within (value ), entirely within (value ), or straddles the boundary — in which case it must take a suffix of and a prefix of (any other shape is suboptimal: truncating to just the crossing suffix/prefix can only raise or match the sum of a non-maximal partial suffix/prefix), so the straddling case is maximised by . Taking the max of all three matches the code.
This four-value node is the standard example of a richer segment tree merge, and it shows up again in Segment Tree and in dynamic DP.
Majority and heavy hitters
Boyer-Moore voting: pair each candidate occurrence with a different element and cancel them. A true majority (more than ) survives every cancellation.
int cand = 0, cnt = 0;
for (int x : a) { if (!cnt) cand = x; cnt += x == cand ? 1 : -1; }
// verify: count(cand) * 2 > n, since a majority may not existProof: Boyer-Moore voting finds the majority if one exists
Think of
cntas a signed tally: each match withcandis , each mismatch is . If a true majority element (occurring times) exists, claim it survives as the finalcand. A prefix wherecnthits exactly 0 pairs off every element it contains into matched steps, and each such pairing consumes exactly one occurrence ofcand’s then-current value against one occurrence of a different value — so the prefix contains equally many occurrences of whatever value wascandduring it as it does of everything else combined, in particular it contains at most half as many occurrences of as of non- elements (equality only when every paired-off value was itself, i.e. the whole prefix alternated against itself, which is impossible since pairs require a mismatch). So discarding a zero-net prefix removes at most as many copies of as it removes of non- elements, meaning ‘s excess (count of minus count of everything else) over the remaining suffix is at least as large as it was over the whole array, hence still positive — is still a majority of the remaining suffix. Since this holds after every reset, can never be fully cancelled, so some occurrence of remains as the uncancelledcandat the end. The verification pass is necessary because if no majority exists, the loop still outputs some candidate.
- More than (Misra-Gries): keep counters; a new value with no free counter decrements all of them. Verify each surviving candidate. time, memory.
Proof: Misra-Gries correctness
Call an element heavy if it occurs more than times. Every decrement step either increments one counter or discards one occurrence each of distinct values (the current element plus the counted ones, all decremented together) — so each such step removes exactly occurrences total, at most one of which can belong to any single value. A heavy value occurs times, so it cannot have every one of its occurrences absorbed into decrement steps (that would require distinct decrement steps occurrence lost per step just for this value, i.e. more than decrement steps, but there are at most decrement steps total since each removes of the occurrences) — so some occurrence of every heavy value survives as an active counter. Since there are only counter slots, there are at most heavy values, consistent with “more than times” being satisfiable by at most disjoint values. Verification is necessary because a surviving candidate’s counted occurrences can undercount its true frequency (some of its occurrences may have been absorbed into decrements), so a candidate might not actually be heavy.
- Sorting: a majority occupies index after sorting.
Proof: a majority element occupies index after sorting
After sorting, all occurrences of (occurring times) form one contiguous block. Suppose for contradiction the block avoids index ; since it’s contiguous, it lies entirely within or entirely within . The first range holds only positions, too few for occurrences. The second holds positions, which is when is even and when is odd — again too few. Both cases contradict occurring times, so the block must cover index .
- Bitwise: with a majority guaranteed, take the majority bit in each position.
| Method | Time | Space | Note |
|---|---|---|---|
| Boyer-Moore voting | optimal; needs verification | ||
| hash map counting | simplest; also gives all counts | ||
| sort, take the middle | the majority must occupy index | ||
| randomised sampling | expected | pick a random element, verify; succeeds with probability per trial | |
| bit-by-bit majority | for each bit, take the majority bit | ||
| divide and conquer | the majority of the whole is a majority of some half |
- Range majority :
- Randomised: sample about 25 random positions and verify each candidate with binary search over that value’s position list. The failure probability is about .
- Deterministic: (candidate, count) pairs merge like Boyer-Moore, so a segment tree gives a candidate in ; verify with position lists.
- Persistent segment tree over values: descend to the half that holds more than .
- Streaming relatives: Count-Min sketch (approximate frequencies), HyperLogLog (distinct count), reservoir sampling.
Circular arrays
| Technique | When |
|---|---|
| double the array | windows that may wrap; cyclic shifts as substrings |
| total minus complement | circular max subarray |
| rotation starting after the minimum prefix | ”a start where every prefix is valid” |
| fix whether element 0 is used | circular DP: solve twice with element 0 forced in or out |
| break at a known point | the cycle has an element that is certainly a cut, such as the global maximum |
Gas station: with , a valid start exists iff , and it’s the position right after the minimum prefix sum. Equivalently, scan with a tank and restart from whenever the tank goes negative: no station in a failed stretch can be a start.
Proof: the gas station one-pass scan is correct
Necessity of is immediate: completing the full circuit requires the tank to return to , and the tank’s net change over the whole circuit is . For sufficiency and the scan: if the tank goes negative while travelling from a tentative start to station , no station can be a valid start. Indeed, starting from , the running tank level on arrival at was (otherwise the scan would already have restarted before ), so a run starting at has no more fuel at every subsequent point than the run from had — in particular, starting from the tank was already negative by station , so starting from it is at least as negative there too, meaning also fails by station . So restarting at after any failure never skips a valid answer, and since guarantees the final restart point (if the scan doesn’t finish from an earlier start) succeeds — the total tank level never dips below the overall minimum prefix sum of , and starting right after that minimum makes every running prefix from there non-negative by definition of “minimum” — one pass suffices.
Cycle lemma (Raney). If integers sum to , exactly one cyclic shift has all partial sums positive. If each and the sum is , exactly shifts do. That proves the ballot theorem and (cyclic shifts of a sequence with ups and downs, made to sum to 1).
Proof: the cycle lemma (case )
Let be the prefix sums, extended periodically by (consistent with each repeating every steps and the total sum being per period). The shift starting right after index (i.e. the sequence ) has all partial sums positive iff for every — i.e. iff is strictly less than every later term . Let be the last index in attaining . Then for every in : for still inside the first period (), because was the last index attaining the minimum, so nothing strictly after it within the period ties or beats it; for , ; for in the second period (, i.e. with ), . So the shift at works. For uniqueness: any other start has, among , some term equal to (since or its periodic copy falls in that range whenever ), and — giving a partial sum (or exactly the periodic copy argument when is the one in range), so that shift fails to be all-positive. Hence exactly one shift works. The general case (, sum ) follows by the same argument applied once per unit of the “extra” rises: there are exactly indices tying for the role plays across the periods needed to accumulate sum , each giving one valid shift.
Minimum refuelling stops: drive until stuck, then retroactively refuel at the biggest station passed (max-heap). The same “take it, regret the worst later” shape appears in regret greedy.
Rotations and in-place rearrangement
- Rotate left by : reverse , reverse , reverse all, since . Normalise with
((k % n) + n) % n.std::rotatedoes it directly.
Proof: the triple-reversal rotation identity
Write the array as with and . Reversing a concatenation reverses and swaps its parts: for any sequences (reversing the whole appends ‘s elements, reversed, before ‘s elements, reversed). So , which is exactly rotated left by . The three in-place reversals (reverse to get , reverse to get , reverse the whole ) therefore produce .
- Apply a permutation in place: walk its cycles, or mark visited by negating or adding to values. The juggling algorithm rotates left by the same way, in place, by walking cycles directly (no reversal) — same time, extra space as the triple-reversal trick, one pass instead of three but with worse cache behaviour, so the reversal method is usually faster in practice despite doing more total work.
- Minimum swaps to sort: number of cycles of the permutation mapping positions to sorted positions.
Proof: minimum swaps to sort is minus the number of cycles
Let be the permutation with = the sorted position of the element currently at (equivalently, decompose the array into the cycles of “where each element needs to go”). A single swap can merge two cycles into one (swapping two elements in different cycles) or split one cycle into two (swapping two elements in the same cycle) — it changes the cycle count by exactly , since a swap is a transposition and composing a permutation with a transposition changes its number of cycles by exactly one (standard fact: a transposition either joins ‘s and ‘s cycles if they were in different cycles, or splits one cycle into two if were in the same cycle). The sorted array is the identity permutation, which has cycles (every element a fixed point, a cycle of length 1). Starting from with cycles, reaching cycles requires splitting cycles down to all singletons: a cycle of length needs exactly swaps to split into singleton cycles (each swap increases the cycle count by at most 1, and splits suffice — repeatedly swap an element with the one it should displace), so the total is swaps, and since every swap changes the cycle count by at most 1, no fewer swaps can go from cycles to cycles.
- Find the duplicate in values from without modifying the array: Floyd’s cycle detection on .
Proof: the duplicate is the entry point of the cycle in
View as a function (indices to values). Since the domain has elements mapping into a range of possible values, is not injective, so some value is hit by two different indices — meaning the functional graph of (each pointing to ) has a vertex with in-degree , which is exactly where two paths merge into a cycle. Floyd’s algorithm finds a point on that cycle, and standard functional-graph argument (see Cycles and Functional Graphs) locates the entry point into the cycle — which is precisely the duplicated value, since that’s the first value reached by two different predecessor chains.
- Missing number(s): XOR of against the array; for two missing numbers, split by any set bit of their XOR.
Proof: the XOR trick finds missing numbers
XOR-ing together with every present array value cancels every value that appears exactly once in the combined multiset (since ), leaving exactly the XOR of the values that appear an odd number of times net — for one missing number, every value except the missing one appears exactly twice (once in the reference range, once in the array) and cancels, leaving exactly the missing value. For two missing numbers : the combined XOR leaves . Any set bit of means differ in bit , so splitting all of and the array by bit puts into different groups, each group now missing exactly one number, solvable by the one-missing-number case independently on each half.
- First missing positive in and memory: cyclic-sort each value into slot , then scan.
Proof: cyclic-sort finds the first missing positive in
The invariant maintained is: after processing index , every value that has been placed reaches its slot if that slot is reachable (i.e. not already correctly occupied), and values outside or duplicates are simply left wherever they land (harmless, since they can never be a missing-positive answer). The swap-into-place loop at each index
ionly terminates (moves toi+1) whena[i]is already correctly placed, out of range, or a duplicate of the target slot’s value — each of the possible values is moved into its correct slot at most once over the whole run (a value only moves when landing in the wrong slot, and each move places some value correctly, so there are at most productive moves total across all starting positions), giving amortized total swaps. After this pass, slot holds for every that was present in the original array; the final scan finds the first slot with , which is exactly the smallest positive integer not present.
- Next permutation: find the last with , swap with the last element greater than it, reverse the suffix. The -th permutation comes from the factorial number system.
Proof: the next-permutation algorithm is correct
The suffix starting at is, by the choice of (the last ascent), weakly decreasing — every permutation of that suffix’s multiset that is lexicographically larger than the current one must therefore change a prefix of the suffix, and the smallest change that increases the value is at position itself: replacing with the smallest element in the suffix that is still (which, since the suffix is decreasing, is found by scanning from the end for the last element greater than ) gives the smallest possible increase at that position. Once is fixed to this new value, the remaining suffix (the old now inserted into the decreasing tail, still decreasing after the swap since we swapped in the smallest valid element from a decreasing sequence) must be arranged in the smallest possible order, which is strictly increasing — exactly what reversing the (still-decreasing) suffix produces.
More in-place patterns:
| Task | Technique |
|---|---|
| remove duplicates from a sorted array | two pointers: unique(all(a)) |
| partition by a predicate | Dutch national flag |
| move zeros to the end | a write pointer trailing a read pointer |
| reverse in groups of | repeated reverse on subranges |
Matrix transformations
Rotations are compositions of transpose and reversals:
| Operation | Recipe | Map for an matrix |
|---|---|---|
| rotate 90° clockwise | transpose, then reverse each row | |
| rotate 90° counter-clockwise | transpose, then reverse row order | |
| rotate 180° | reverse row order and each row | |
| flip top-bottom / left-right | reverse row order / each row | |
| main-diagonal mirror | transpose | |
| anti-diagonal mirror | reverse rows, transpose, reverse rows |
In the transpose loop start j at i + 1, otherwise every pair is swapped twice. For an matrix, allocate a result: res[j][r-1-i] = m[i][j].
Derive, don't memorise
Rotate the corner in your head and see where it lands. Transpose sends ; reversing rows sends . Composing gives , which is exactly clockwise rotation. Two seconds of checking beats a memorised table you might have backwards.
Proof: the transpose-then-reverse recipes rotate correctly
Transpose sends . Applying “reverse each row” afterward acts on the transposed matrix’s own row/column indices ; substituting the transposed position gives . So the composition sends , matching the table’s clockwise formula exactly. Sanity check against the corners: the top-left (top-right) and the top-right (bottom-right) — consistent with a clockwise quarter turn, where the top edge swings down to become the right edge. The other three recipes (counter-clockwise: transpose then reverse row order; 180°: reverse rows and reverse each row; flips: a single reversal) compose the same two primitives ( for transpose, for reversing row order, for reversing each row) and are checked the same way.
Diagonals: anti-diagonals are indexed by and main diagonals by (offset by ). Grid DPs that move right/down process anti-diagonals in order.
Spiral order needs the two guards before the bottom row and left column passes, or a single remaining row or column is printed twice.