</> MAANG.io
coding interview · 201

Intermediate

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/170 solved 0% complete

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.

flowchart TD A["looks like try-every-option"] --> B["what can one pass accumulate?"] B --> C["Flip String: ones seen so far
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 running min decides 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

  1. Ask what one pass can maintain so the answer is immediate everywhere.
  2. A min of two running costs replaces searching for a boundary.
  3. Edges behave differently from the middle in every gap problem.
  4. A small value range means bucket by value, and the pair count becomes bucket arithmetic.
  5. Correct for self-pairs when both items come from the same bucket.
  6. Read the constraints for the trick. "Ages are 1 to 120" is the whole hint.
  7. 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.

Friends Of Appropriate Ages

Core idea: Don't loop over people — loop over ages. There are only 120 possible ages, so instead of comparing all pairs of people, tally how many people sit at each age and compare the 120 × 120 grid of age-pairs. The three tangled rejection rules collapse into one clean window: a person of age a sends to a person of age b exactly when 0.5·a + 7 < b ≤ a. For each such ordered age-pair, every age-a person can request every age-b person, so add count[a] · count[b] — subtracting count[a] when a == b because nobody requests themself.


The problem, rephrased

You run a social app where each user has an age. Person x will send a friend request to a different person y unless any of these is true:

  1. age[y] <= 0.5 · age[x] + 7y is too young for x.
  2. age[y] > age[x]y is older than x.
  3. age[y] > 100 and age[x] < 100 — a quirky "centenarian" rule.

Given the array ages (each value in 1..120), return the total number of friend requests sent. Requests are directed: x → y and y → x are counted separately.

A fresh scenario

Think of a dating-app outreach counter. Every user can "ping" another user, but the app blocks pings that break its age-etiquette rules (don't ping someone way younger than you, don't ping someone older, plus a legacy rule about the 100+ crowd). Management doesn't care who pinged whom — they want the total ping volume for capacity planning. Since age is the only thing the rules look at, two users of the same age are interchangeable: if a 30-year-old can ping a 25-year-old, then every 30-year-old can ping every 25-year-old. So you never reason about individuals — you reason about age groups and multiply.

ages Output Why
[16,16] 2 16 → 16 is allowed (window is 15 < b ≤ 16); both directions count
[16,17,18] 2 Only 17→16 and 18→17 survive the window; the rest are too young or older
[20,30,100,110,120] 3 Just three ordered pairs clear all the rules
[73,73] 2 Same age, both can request each other

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

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.