2Brute2Best
🔥0

Sliding Window Median

Hard·LeetCode #480Interview-Level
LeetCode
30-Second Mental ModelTwo Heaps — Sliding median (two heaps + lazy deletion)
Complexity:O(N · K log K)→O(N log N)
Key Insight

Split the window into low, a max-heap with the smaller half, and high, a min-heap with the larger half. Their two tops are the middle. A heap cannot delete a value from its middle, so a leaving value is marked as stale in a delayed map. A stale value only matters when it reaches a top, so it is dropped then. A balance counter tracks valid values only.

Core Validity Invariant

The state is valid when low holds ceil(k/2) valid values, all ≤ every valid value in high, and neither top is stale.

01

Problem

You get an array of integers nums and a window size k. Put a window of size k at the left end of the array. Then slide it one step to the right at a time, until its right edge reaches the last element.

For each window position, find its median: sort the window's values and take the middle one. If the window has an even number of values, the median is the average of the two middle values.

Return the medians in window order. An answer within 10^-5 of the true value is accepted.

Input

An integer array nums and an integer k, the window size.

Output

An array of n − k + 1 numbers. Entry i is the median of the window that starts at index i.

Example 1
Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
Output: [1, -1, -1, 3, 5, 6]

The windows are [1,3,-1], [3,-1,-3], [-1,-3,5], [-3,5,3], [5,3,6] and [3,6,7]. Their medians are 1, -1, -1, 3, 5 and 6.

Example 2
Input: nums = [1, 2, 3, 4, 2, 3, 1, 4, 2], k = 3
Output: [2, 3, 3, 3, 2, 3, 2]

Every window has 3 values, so the median is the middle one after sorting. For example [3,4,2] sorts to [2,3,4], and its median is 3.

Example 3
Input: nums = [5, 9], k = 2
Output: [7]

There is one window, the whole array. Its two middle values are 5 and 9, so the median is (5 + 9) / 2 = 7.

Constraints
  • • 1 ≤ k ≤ nums.length ≤ 10^5
  • • −2^31 ≤ nums[i] ≤ 2^31 − 1
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    A median changes when values come and go. What data structure can give me the middle of a set quickly while the set changes?

  2. 2.

    If I sorted every window again, how much of that work would repeat from one window to the next?

  3. 3.

    A slide removes one value and adds one. Can I keep the window sorted and fix only the spots that changed?

  4. 4.

    A priority_queue only shows me its top. What can I do when a value that is not on top has to leave?

  5. 5.

    For an odd window size, how many values should the smaller half hold compared with the larger half? For an even size, which two values give the median?

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceCopy each window, sort the copy, and read the middleO(N · K log K)O(K)
BetterBinary search finds the spot for each insert and erase, so the window stays sortedO(N · K)O(K)
OptimalTwo heaps give the middle; a leaving value is marked and dropped only when it reaches a topO(N log N)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “median of each window”The middle of a set that keeps changing. Two heaps keep the middle at their tops.
  • “window of size k slides”Values come in and go out. A plain running median only adds values, so it cannot handle leaving values.
  • “return the medians in window order”One answer per window position. After each slide, read the tops.
Median of a moving set+two heaps for the two halves+lazy deletion for leaving values→Two Heaps — Sliding median (two heaps + lazy deletion)

Find Median from Data Stream only inserts, so two heaps with no deletions are enough there. This question also has values that leave the window, and a heap cannot remove a value from its middle. The fix is lazy deletion: a leaving value is only marked, and it is dropped when it reaches a top. The validity rule also differs from that neighbour: a value inside a heap is not always in the window, so only the values still in the window count toward balance.

10

Don't make this mistake

Pattern confusion: a stale value still counted in the sizes
✗ delayed[out]++; prune(high); // balance is not changed
✓ balance++; // high loses a valid value delayed[out]++; prune(high);

This is the running-median habit. A heap cannot erase from its middle, so the code marks the value and prunes the top, but it forgets to fix the count. Dry run: nums = [6, 1, 9, 4, 7, 3, 8, 2, 5], k = 4. The window [6, 1, 9, 4] has low {1, 4} and high {6, 9}, with balance 0. When 6 leaves, it is on top of high and gets pruned. With balance still 0, the code thinks the halves are level, but high has lost a value. Then 7 enters high, balance drops to −1, and the rebalance moves 7 into low. The median of the window [1, 9, 4, 7] comes out as (7 + 9) / 2 = 8. The right answer is (4 + 7) / 2 = 5.5.

Reading a top without pruning it first
✗ balance++; delayed[out]++; // no prune(high)
✓ balance++; delayed[out]++; prune(high);

A stale value on top looks like a valid value to the code. Same dry run: after 6 leaves, high is {6, 9}, and 7 enters, so high becomes {6, 7, 9} with the stale 6 on top. The median then reads (4 + 6) / 2 = 5 for the window [1, 9, 4, 7], but the right answer is 5.5. Prune the side a value left from, right away, so its top is always valid.

Using < instead of <= when a leaving value ties with low's top
✗ if (!low.empty() && out < low.top()) {
✓ if (!low.empty() && out <= low.top()) {

A value equal to low's top is still a valid value of low, because low holds the smaller half. With <, the code takes it off high's count instead. For nums = [1, 4, 2, 0, 3] and k = 4, the window [1, 4, 2, 0] has low {0, 1} with top 1. When 1 leaves, it must be counted on the low side. The next window [4, 2, 0, 3] then has median (2 + 3) / 2 = 2.5, but the wrong code returns 1.5.

Averaging two ints overflows
✗ ans.push_back((low.top() + high.top()) / 2.0);
✓ ans.push_back(((double)low.top() + high.top()) / 2.0);

The sum is calculated as an int before the division. With nums = [2147483647, 2147483647] and k = 2, the sum 4294967294 does not fit in an int. In practice it wraps to −2, and the answer becomes −1.0. The right answer is 2147483647.0. Cast one value to double before the addition.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Two Heaps translate into production architectures and low-level memory mechanics.