Array Tricks
What is this?
Each of these looks like it needs to try every possibility, and each is one pass with one or two counters. The recurring move is to ask what a single scan could accumulate that makes the answer obvious at every position — the number of flips needed if the boundary were here, the size of the gap ending here, how many people fall into a given age bucket.
vs flips needed if we stop here"] B --> D["Maximise Distance: gap length,
plus the two EDGE cases"] B --> E["Friends of Ages: bucket by age
→ 120 x 120 instead of n x n"]
💡 Fun fact: Friends of Appropriate Ages has up to 20,000 people and looks quadratic — but ages are bounded to 1–120. Bucketing by age turns "every pair of people" into "every pair of ages", which is at most 120 × 120 = 14,400 combinations regardless of population, with the counts supplying the multiplication. Whenever a problem bounds a value far below the number of items, that bound is an invitation to count by value instead of by item.
🔓 The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: find the quantity a single left-to-right pass can maintain so that the answer at every position is immediate — a running count, a gap length, or a tally by value rather than by item.
1. A running count that decides a boundary
A monotone string is some 0s followed by some 1s, so the answer is the best place to draw the boundary. At any position, the flips needed are (the 1s to the left, which must become 0s) plus (the 0s to the right, which must become 1s). One pass maintains both implicitly:
ones = flips = 0
for ch in s:
if ch == '1':
ones += 1 # a 1 here is free if the boundary is after it
else:
flips = min(flips + 1, ones) # flip this 0, or flip every 1 seen so far
return flips
The min is the whole algorithm. Either you keep this 0 and pay to flip it, or you decide the boundary is behind you and pay to flip all the 1s instead — and you never need to know where the boundary actually is.
2. The gaps, and the edges
The best empty seat is in the middle of the largest interior gap — but the first and last runs behave differently, because a run of empty seats at either end is not surrounded on both sides:
| Situation | Best distance |
|---|---|
gap of length g between two occupied seats |
(g + 1) // 2 |
g empty seats at the start |
g — sit at the very front |
g empty seats at the end |
g — sit at the very back |
Forgetting the edge runs is the standard failure, and both are needed: an array like [1,0,0,0] has its answer at the end, not in a middle gap.
3. Bucket by value when the range is small
count = [0] * 121 # ages 1..120
for age in ages:
count[age] += 1
total = 0
for a in range(1, 121):
for b in range(1, 121):
if request_allowed(a, b): # the problem's three conditions
total += count[a] * count[b] - (count[a] if a == b else 0)
The a == b correction removes self-requests: within one age bucket there are c × (c − 1) ordered pairs, not c × c.
4. A 30-second worked example (Flip String to Monotone Increasing)
s = "00110"
ch ones flips
0 0 min(0+1, 0) = 0 keep as 0, nothing to flip
0 0 min(0+1, 0) = 0
1 1 0 a 1 after 0s is free
1 2 0
0 2 min(0+1, 2) = 1 flip this 0 → cheaper than flipping two 1s
answer = 1
The final min chooses between flipping this single 0 (cost 1) and flipping the two 1s before it (cost 2). One flip gives "00111", which is monotone ✓.
5. Where you'll actually meet this
- Data cleaning. Minimum edits to make a sequence monotone appears in sensor smoothing and validation.
- Seating and allocation. Placing a new item to maximise distance from existing ones — table seating, base-station siting, load spreading.
- Demographic and matching analytics. Bucketed pairwise counting is how any large matching or eligibility calculation is actually done.
- Histogram statistics. Whenever a value range is far smaller than the item count, counting by value is the standard optimisation.
- Threshold detection. Running counters that decide a boundary in one pass, over streams.
6. Problems in this chapter
▶ Flip String to Monotone Increasing
Minimum flips to make a binary string monotone. Track the 1s seen and take min(flips + 1, ones) at each 0.
Pattern: running-count boundary decision. Target: O(n) time, O(1) space.
▶ Maximize Distance to Closest Person
Best seat given occupied seats. Largest interior gap gives (g + 1) // 2; leading and trailing runs give g.
Pattern: gap scan with edge cases. Target: O(n) time, O(1) space.
▶ Friends of Appropriate Ages
Count friend requests under three age conditions. Bucket by age and count pairs of buckets.
Pattern: bucket by a bounded value. Target: O(n + 120²) time, O(120) space.
7. Common pitfalls 🚫
- Trying every boundary position.
O(n²)when a runningmindecides it in one pass. - Missing the edge runs. Empty seats at the start or end give distance
g, not(g+1)//2. - Rounding the interior gap wrong.
(g + 1) // 2— check it against a gap of 3, where the answer is 2. - Comparing every pair of people. The age bound is the hint; bucket instead.
- Counting self-requests. Within one age bucket the count is
c × (c − 1). - Assuming at least one occupied seat. The problem guarantees it, but say that you checked.
- Allocating 20,000 buckets instead of 121. Size the array by the value range, not the input length.
8. Key takeaways
- Ask what one pass can maintain so the answer is immediate everywhere.
- A
minof two running costs replaces searching for a boundary. - Edges behave differently from the middle in every gap problem.
- A small value range means bucket by value, and the pair count becomes bucket arithmetic.
- Correct for self-pairs when both items come from the same bucket.
- Read the constraints for the trick. "Ages are 1 to 120" is the whole hint.
- Why interviewers like it: the quadratic solution is immediate, so the entire signal is whether you spot the accumulator or the bound that removes it.
Order: Flip String to Monotone Increasing → Maximize Distance to Closest Person → Friends of Appropriate Ages.
Flip String to Monotone Increasing
Core idea: A monotone-increasing binary string is always
0…0then1…1— some zeros, then some ones, never a0after a1. Scan left to right keeping one number:ones, how many1s you've passed so far. Every time you hit a0, it's "behind" all those1s, so it breaks monotonicity and you must pay: either flip this0into a1(cost+1, keeps it on the right side) or flip every earlier1back to0(cost= ones, pushes the boundary past this point). Keep whichever running total is cheaper. Oneminper zero, computed once, reused forever.
This is a running / prefix DP: a single scan where the state at position i is a cheap function of the state at i-1. The whole problem collapses to a recurring decision made at each 0, and that decision is what makes it a "tricky algorithm" — the trick is realizing you never need to know where the 0/1 boundary goes, only the cheapest cost to have everything-so-far be monotone.
Problem, rephrased
You're handed a binary string s. In one operation you may flip a single character — turn a 0 into a 1, or a 1 into a 0. You want the string to become monotone increasing: reading left to right, once you've seen a 1 you must never see a 0 again. Equivalently, the final string must match the pattern 0*1* (any run of zeros followed by any run of ones; either run may be empty). Return the minimum number of flips to get there.
Think of a turnstile log where 0 = "before opening time" and 1 = "after opening time". A clean day reads 0 0 0 1 1 1 — closed, then open, never flipping back. A few stray entries (0 0 1 0 1) mean someone mis-stamped; you want the fewest corrections to make the log consistent.
s = "0 1 0 1 1 0"
^ ^
a 0 AFTER a 1 <- these break "monotone"
Once any 0 sits to the right of a 1, the string is no longer of the form 0*1*, and each such conflict costs a flip to resolve — the question is which flip is cheapest as you go.
Inputs / outputs
s |
a cheapest monotone target | answer |
|---|---|---|
"00110" |
"00111" (flip last 0) |
1 |
"010110" |
"000000" or "011111" |
2 |
"00011000" |
"00011111" (flip two 0s) |
2 |
"11" |
already monotone | 0 |
"0" |
already monotone | 0 |
Notice in "010110" there's a tie between flipping everything down to all-zeros and flipping up to 0 1 1 1 1 1 — both cost 2. The algorithm doesn't care which target it "chooses"; it only tracks the minimum cost.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson