</> 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

Monotonic Stack

What is this?

A queue of people of different heights, each looking forward. You can see the person immediately ahead, and anyone taller than everyone between you and them — your view is blocked the moment someone at least as tall stands in the way. Count what each person sees.

Walk the queue from the back with a stack of decreasing heights, and every pop is a person this one can see. The answer is not something you compute after the sweep; it accumulates during it.

flowchart TD A["keep a monotonic stack of unresolved candidates"] --> B["a new element arrives"] B --> C{"does it dominate the top?"} C -->|yes| D["POP — and the pop IS an answer"] D --> D1["a sight line ends"] D --> D2["a collision is fixed"] D --> D3["a worse digit is discarded"] C -->|no| E["push and wait"] D --> C

💡 Fun fact: Create Maximum Number is two techniques stacked. First, picking the largest possible k-digit subsequence from one array is a monotonic stack with a budget: pop a smaller digit only while enough digits remain to still fill the quota. Then the two selections are merged by repeatedly taking from whichever array is lexicographically greater from its current position onward — comparing suffixes, not single digits, because equal leading digits must be broken by what follows. Trying every split of k between the two arrays wraps both.

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


The one-line idea: keep the candidates that could still matter, in an order that makes the irrelevant ones obvious, and treat every pop as the moment an answer is produced rather than as bookkeeping.


1. The pop as a count

res = [0] * n
stack = []                              # heights, strictly decreasing
for i in range(n - 1, -1, -1):
    count = 0
    while stack and stack[-1] < heights[i]:
        stack.pop(); count += 1         # each shorter person ahead is visible
    if stack:
        count += 1                      # plus the first person at least as tall
    res[i] = count
    stack.append(heights[i])

The if stack: count += 1 is the whole subtlety. Everyone popped is visible, and the first person not popped — the one at least as tall — is also visible, because they are what blocks the view rather than being hidden by it. Forgetting that undercounts by exactly one everywhere the view is blocked.

2. The pop as a collision

Car Fleet II processes cars from the back with a stack of cars ahead that are still relevant. A car ahead becomes irrelevant when either:

  • it is faster or equal, so it can never be caught; or
  • it collides with something else first, at a time earlier than when this car would reach it.

Both are pops. What remains on top after the popping is the car this one actually hits, and the collision time is (posAhead − pos) / (speed − speedAhead). The stack is therefore monotonic in a quantity — collision time — that does not exist in the input and is derived as the sweep proceeds.

3. The wrap

Next Greater Element II is the ordinary next-greater sweep over a circular array. Iterate 2n times indexing with % n, and only push during the first pass — the second pass exists solely to resolve elements left unmatched, not to add new candidates.


4. A 30-second worked example (visible people)

heights = [10, 6, 8, 5, 11, 9]

i=5  h=9   stack empty          → count 0                  push 9    [9]
i=4  h=11  pop 9 (count 1)      stack empty → no blocker    → 1      push 11   [11]
i=3  h=5   11 > 5, no pops      stack non-empty → +1        → 1      push 5    [11,5]
i=2  h=8   pop 5 (count 1)      11 > 8 → +1                 → 2      push 8    [11,8]
i=1  h=6   8 > 6, no pops       stack non-empty → +1        → 1      push 6    [11,8,6]
i=0  h=10  pop 6, pop 8 (2)     11 > 10 → +1                → 3      push 10   [11,10]

result: [3, 1, 2, 1, 1, 0]

Check person 0 by hand: they see 6, then 8 (taller than 6), then 11 — three people. The 5 and the 9 are hidden behind taller people. ✓


5. Where you'll actually meet this

  • Traffic simulation. Convoy formation and collision prediction, which is Car Fleet II with real vehicles and real timestamps.
  • Rendering and occlusion. Determining which objects along a sight line are visible.
  • Financial data. Span indicators — how many consecutive prior periods a value dominates — computed incrementally over a stream.
  • Number formatting. Selecting the largest or smallest representation under a digit budget.
  • Skyline and terrain analysis. Visibility from a point across a height profile.

6. Problems in this chapter

▶ Number of Visible People in a Queue

How many people each person can see. Sweep from the back with a decreasing stack; every pop is visible, plus the first blocker.
Pattern: pop-as-count. Target: O(n) time, O(n) space.

▶ Next Greater Element II

Next greater element in a circular array. Iterate 2n with % n, pushing only on the first pass.
Pattern: circular monotonic stack. Target: O(n) time, O(n) space.

▶ Car Fleet II

For each car, when it collides with the car ahead. Sweep from the back, popping cars that are faster or that collide earlier than this car could reach them.
Pattern: monotonic in a derived collision time. Target: O(n) time, O(n) space.

▶ Create Maximum Number

Largest k-digit number from two arrays preserving order. Monotonic-stack selection with a budget from each array, then a suffix-comparing greedy merge, over every split of k.
Pattern: budgeted selection + lexicographic merge. Target: O((m+n)³) worst case.


7. Common pitfalls 🚫

  • Forgetting the blocker. The first person not popped is still visible; missing them undercounts by one.
  • Using <= instead of < in the visibility pop. Equal heights block the view, so they must not be popped.
  • Pushing during the second pass of the circular sweep, which invents elements that do not exist.
  • Comparing single digits in the merge. Equal leading digits must be resolved by comparing the remaining suffixes.
  • Ignoring the budget in the digit selection: you may only pop while enough digits remain to reach k.
  • Dividing by zero in the collision time when two cars share a speed — that case is a pop, not a computation.
  • Claiming O(n²) for the monotonic sweeps. Each index enters and leaves once; say so before being asked.

8. Key takeaways

  1. The pop is the answer, not a step toward it. Decide its meaning first.
  2. The blocker counts too — the element that stops the sweep is part of the result.
  3. A stack can be monotonic in a derived value, like a collision time computed during the walk.
  4. Circular means 2n iterations, not a doubled array, and no pushes on the second pass.
  5. Budgeted popping is how selection problems keep enough digits to finish.
  6. Compare suffixes, not characters, when merging for lexicographic maximum.
  7. Why interviewers like it: the sweep is familiar, so all the difficulty is in modelling — and Car Fleet II in particular cannot be pattern-matched from easier problems.

Order: Number of Visible People in a Queue → Next Greater Element II → Car Fleet II → Create Maximum Number.

Calculating Vehicle Collision Times in Fleet Formation

Core idea: Work from the back of the road forwards, keeping a stack of the cars ahead that can still be collided with. A faster car catches a slower one at time (pos_ahead − pos) / (speed − speed_ahead), but that answer is only valid if the car ahead has not already merged into something else first. So before computing, pop any car ahead that is not slower (it will never be caught) and any car whose own collision happens before the candidate collision time — because by then it has joined a fleet and is moving at a different speed. What is left on top is the real target. Each car is pushed and popped once: O(n).

Problem Statement

We have several vehicles moving in the same direction along a straight road. Each vehicle has a specific position and initial speed. When two vehicles collide, they merge into a single fleet with the speed of the slower vehicle. We need to determine for each vehicle the time it takes to collide with the next vehicle in front, or indicate if no collision occurs.

Key Details

  • Vehicles are points on a line with increasing positions.
  • Upon collision, the fleet adopts the minimum speed among merged vehicles.
  • We must compute collision times or return -1 if no collision happens with the immediate next vehicle.

Sample Examples

Example 1:
![Example 1: cars [[1,2],[2,1],[4,3],[7,2]] produce collision times [1.00, -1.00, 3.00, -1.00]](https://maangioassets.blob.core.windows.net/diagrams/coding-interview/301/chapters/stack/monotonic-stack/car-fleet-ii-diagram-1.svg)
Here:

  • Car 0 (pos=1, speed=2) collides with Car 1 (pos=2, speed=1) at time 1.00
  • Car 1 merges with Car 0, new fleet at pos=2, speed=1
  • Car 2 (pos=4, speed=3) drives ahead
  • Car 3 (pos=7, speed=2) collides with the merged fleet at the Car 1 position? Wait, let's calculate properly.

The times are for each car to collide with its immediate next one, and the merge affects future calculations.

Example 2:
![Example 2: cars [[3,4],[5,4],[6,3],[9,1]] produce collision times [2.00, 1.00, 1.50, -1.00]](https://maangioassets.blob.core.windows.net/diagrams/coding-interview/301/chapters/stack/monotonic-stack/car-fleet-ii-diagram-2.svg)

This can also apply to packet routing in networks where delays merge, or stock price simulations where trends converge.

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.