</> MAANG.io
coding interview · 301

Advanced

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

0/95 solved 0% complete

Cycle and Order

What is this?

Each problem here exploits a relationship between an array's values and its indices. When values are drawn from the index range, following i → nums[i] is a linked list. When the array is a permutation, the running maximum tells you where a sorted block can end. And when you want to count pairs across an ordering, the merge step of a merge sort already visits exactly the right pairs.

flowchart TD A["values relate to indices"] --> B["Find the Duplicate
i → nums[i] is a linked list"] B --> B1["n+1 values in 1..n → a cycle exists"] B1 --> B2["Floyd finds its entrance = the duplicate"] A --> C["Max Chunks
prefix max == index → boundary"] A --> D["Reverse Pairs
count during the merge"] D --> D1["both halves sorted → one scan per merge"]

💡 Fun fact: Max Chunks To Make Sorted has a one-line solution with a one-sentence proof. On a permutation of 0 … n−1, a chunk can end at index i exactly when max(arr[0..i]) == i — because that means the first i+1 positions hold precisely the values 0 … i, so sorting them internally puts every one where it belongs and nothing needs to cross the boundary. Count those positions and you have the answer. The whole problem is max and a comparison, and it takes longer to justify than to write.

🔓 The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: ask what the indices tell you about the values, or the values about the indices. All three of these are invisible until you look at the array as a structure rather than as a list.


1. The array as a linked list

slow = fast = nums[0]
while True:
    slow = nums[slow]; fast = nums[nums[fast]]      # phase 1: find a meeting point
    if slow == fast: break

slow = nums[0]
while slow != fast:                                  # phase 2: find the cycle entrance
    slow = nums[slow]; fast = nums[fast]
return slow

n+1 values drawn from 1 … n guarantee two indices pointing to the same place, so the traversal must cycle — and the cycle's entrance is the value with two arrows into it, which is the duplicate.

Phase 2 is the part worth being able to justify. Once the pointers meet, restarting one at the head and advancing both one step at a time makes them meet exactly at the cycle entrance. The reason is arithmetic: at the meeting point, the distance from the head to the entrance equals the distance from the meeting point to the entrance, modulo the cycle length. Interviewers ask for that argument, not for the code.

The constraints are what point here: O(1) space rules out a set, and "do not modify the array" rules out sorting and index marking. What is left is Floyd.

2. The prefix maximum

mx = chunks = 0
for i, v in enumerate(arr):
    mx = max(mx, v)
    if mx == i: chunks += 1
return chunks

Two variables, no allocation. Note this is the version for a permutation of 0 … n−1; the variant that allows duplicates needs a comparison of prefix maxima against suffix minima instead, which is worth naming as the natural follow-up question.

3. Counting during the merge

Reverse Pairs counts pairs where nums[i] > 2 · nums[j] and i < j. During a merge, both halves are already sorted, so a single two-pointer scan counts every cross-half pair before the merge itself runs:

j = 0
for x in left:
    while j < len(right) and x > 2 * right[j]: j += 1
    count += j                                       # every right[0..j) pairs with x

j never resets between iterations of the outer loop — as x increases, so does the count of right values it dominates — which is what makes the counting linear rather than quadratic. The count must be taken before merging, while the two halves are still separate, and the doubling means overflow is a real consideration in fixed-width languages.


4. A 30-second worked example (the duplicate)

nums = [1, 3, 4, 2, 2] — read as a linked list from index 0:

0 → nums[0]=1 → nums[1]=3 → nums[3]=2 → nums[2]=4 → nums[4]=2 → nums[2]=4 → …

as a picture:      0 → 1 → 3 → 2 → 4
                                ↑    ↓
                                 ────

the cycle is 2 → 4 → 2, and its ENTRANCE is 2
two arrows point at 2: from index 3 and from index 4 — the duplicated value

answer: 2

The duplicate is the entrance precisely because it is the one node with two predecessors — which is exactly what "this value appears twice" means once the array is drawn this way. No counting, no sorting, no extra memory.


5. Where you'll actually meet this

  • Data integrity checks. Finding a duplicate in a read-only stream under a memory bound is a genuine production constraint.
  • Ranking and recommendation analysis. Inversion and reverse-pair counts measure how far two orderings differ.
  • Parallel and distributed sorting. Maximum chunk decomposition is exactly how work is split for independent sorting.
  • Cycle detection in general. Floyd's algorithm detects loops in linked structures, state machines and hash chains.
  • Database query analysis. Counting out-of-order pairs is how sortedness of an index is estimated.

6. Problems in this chapter

▶ Find the Duplicate Number

Find the repeated value without modifying the array or using extra space. Treat i → nums[i] as a linked list and locate the cycle entrance with Floyd's algorithm.
Pattern: cycle detection on an implicit list. Target: O(n) time, O(1) space.

▶ Reverse Pairs

Count pairs with i < j and nums[i] > 2 · nums[j]. Count cross-half pairs during a merge sort, before merging.
Pattern: counting inside a merge sort. Target: O(n log n) time, O(n) space.

▶ Max Chunks To Make Sorted

Most chunks that can be sorted independently. Count the positions where the prefix maximum equals the index.
Pattern: prefix maximum as a boundary test. Target: O(n) time, O(1) space.


7. Common pitfalls 🚫

  • Reaching for a hash set. The constraints exist to rule it out; they are pointing at Floyd.
  • Skipping phase 2 of Floyd. The meeting point is not the entrance, and the distinction is the question.
  • Advancing the fast pointer once in phase 1 or twice in phase 2. It is two, then one.
  • Counting reverse pairs after merging, once the halves are indistinguishable.
  • Resetting j on each outer iteration, which makes the count quadratic.
  • Overflow on 2 · nums[j] in fixed-width languages.
  • Applying the prefix-maximum rule to an array with duplicates, where it does not hold and a different comparison is needed.

8. Key takeaways

  1. When values index the array, the array is a graph. Draw it.
  2. The cycle entrance is the duplicate, because it is the node with two predecessors.
  3. Constraints select the algorithm. O(1) space and read-only leave exactly one option.
  4. Merge sort counts for free — the sorted halves make cross-pairs countable in one scan.
  5. A prefix maximum equal to its index means the prefix is complete and can be closed.
  6. Know the follow-up. Duplicates break the chunk rule, and the fix is prefix max versus suffix min.
  7. Why interviewers like it: each has a short solution and a real proof, so they can ask why and get a genuine answer or not.

Order: Find the Duplicate Number → Reverse Pairs → Max Chunks To Make Sorted.

Max Chunks To Make Sorted

Core idea: You have a permutation of 0..n-1 and you want to cut it into the maximum number of contiguous chunks so that if you sort each chunk in place and glue the chunks back together, the whole array comes out fully sorted. The trick is almost embarrassingly small once you see it: scan left to right tracking the running maximum of the values seen so far. The moment running_max == i (the current index), the prefix arr[0..i] contains exactly the values 0..i — no more, no less — so it can be closed off as its own chunk and, once sorted, it will occupy exactly positions 0..i in the final array. Every index where this happens is a legal chunk boundary; count those boundaries and you have the answer. One pass, O(n) time, O(1) space.


The problem, rephrased

Picture a conveyor belt of n numbered bins, one bin per integer in 0..n-1, but delivered out of order. You want to slice the belt into as many consecutive segments as possible such that sorting each segment on its own — and only locally — leaves the entire belt in ascending order. A segment can only be "self-contained" if it holds precisely the numbers that belong in the slots it covers; if even one number that belongs earlier got pushed into a later segment, you'd be forced to merge segments together.

Formally: given arr, a permutation of the integers 0, 1, ..., n-1, split arr into the largest possible number of chunks (contiguous subarrays) so that sorting each chunk individually and concatenating them yields the sorted array [0, 1, ..., n-1]. Return that maximum count.

This is LeetCode 769 — Max Chunks To Make Sorted.

Input Means Output
arr = [4,3,2,1,0] everything is "in front of" where it belongs 1 (one chunk: sort the whole thing)
arr = [1,0,2,3,4] [1,0] fixes itself, then each lone value is set 4 (chunks [1,0] [2] [3] [4])
arr = [0,1,2,3,4] already sorted 5 (each element is its own chunk)

The boundaries are the whole story: a chunk closes exactly where the biggest value seen so far has finally caught up to the current position.


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.