Twice-the-Median of Every Window Along a Sensor Trace

Solve this Problem
Hard40–45 min
Topics
Companies
Practice:LeetCode ↗

A sensor produces a trace of integer readings. For every window of k consecutive readings, report twice the median of the window (so the answer is always an integer): for an odd k that is twice the middle value, and for an even k it is the sum of the two middle values.

Sorting each window separately repeats a lot of work, since consecutive windows share all but one reading. Two heaps — a max-heap for the smaller half and a min-heap for the larger half — keep the median at their roots. Because a heap can't delete an arbitrary element cheaply, readings that slide out of the window are only marked expired and quietly discarded when they reach the top.

Test Case 1:

Input:readings = [5, 2, 9, 1, 7], k = 3
Output:[10, 4, 14]
Explanation:Windows [5,2,9] → sorted [2,5,9], median 5 → 10. [2,9,1] → [1,2,9], median 2 → 4. [9,1,7] → [1,7,9], median 7 → 14.

Test Case 2:

Input:readings = [4, 4, 4, 4], k = 2
Output:[8, 8, 8]
Explanation:Each window of two 4s has middle values 4 and 4, so twice the median is 8.

Test Case 3:

Input:readings = [3, -3, 8], k = 1
Output:[6, -6, 16]
Explanation:With k = 1 every window is a single reading, so the answer is simply twice each reading.

Constraints

  • ◆1 ≤ k ≤ readings.length ≤ 60
  • ◆-1000 ≤ readings[i] ≤ 1000, and readings may repeat
  • ◆For every window of k consecutive readings (left to right), report twice the median: for an odd k this is 2 × the middle value; for an even k it is the sum of the two middle values
  • ◆Return the answers in window order — readings.length − k + 1 of them
🚀

Try the Dry Run

Don't just read the solution — watch it execute, one step at a time.

Approach & Solutions

Brute Force — Copy and Sort Every Window

Brute

For each of the n − k + 1 windows, copy its k readings, sort the copy, and read the middle: the doubled median is window[(k−1)/2] + window[k/2], which handles odd and even k alike. It is simple and correct, but every window is sorted from scratch even though consecutive windows share k − 1 readings.

TimeO(n · k log k)
SpaceO(k)
1class Solution { 2 public List<Integer> windowTwiceMedians(int[] readings, int k) { 3 List<Integer> answers = new ArrayList<>(); 4 for (int start = 0; start + k <= readings.length; start++) { 5 int[] window = Arrays.copyOfRange(readings, start, start + k); 6 Arrays.sort(window); 7 answers.add(window[(k - 1) / 2] + window[k / 2]); 8 } 9 return answers; 10 } 11}

Optimal — Two Heaps With Lazy Deletion

Optimal

Keep the window's smaller half in a max-heap (low) and larger half in a min-heap (high), with low holding the same number of readings as high or one more — so the median sits at the roots. Sliding the window means adding one reading and removing another, but heaps cannot delete an arbitrary element cheaply. Instead, deletion is lazy: mark the outgoing reading's index as expired, adjust the size of the heap it lives in (tracked in side[]), and only discard expired entries when they surface at a heap's top. Heaps store (value, index) pairs so an entry's expiry can be checked. After each slide, rebalance by moving valid tops between the heaps until the sizes are right, then read the median from the roots.

TimeO(n log n)
SpaceO(n)
1class Solution { 2 public List<Integer> windowTwiceMedians(int[] readings, int k) { 3 int n = readings.length; 4 PriorityQueue<int[]> low = new PriorityQueue<>((a, b) -> b[0] - a[0]); 5 PriorityQueue<int[]> high = new PriorityQueue<>((a, b) -> a[0] - b[0]); 6 boolean[] expired = new boolean[n]; 7 int[] side = new int[n]; 8 int lowSize = 0, highSize = 0; 9 List<Integer> answers = new ArrayList<>(); 10 for (int i = 0; i < n; i++) { 11 if (i >= k) { 12 int old = i - k; 13 expired[old] = true; 14 if (side[old] == 0) lowSize--; 15 else highSize--; 16 } 17 while (!low.isEmpty() && expired[low.peek()[1]]) low.poll(); 18 if (low.isEmpty() || readings[i] <= low.peek()[0]) { 19 low.offer(new int[]{readings[i], i}); 20 side[i] = 0; 21 lowSize++; 22 } else { 23 high.offer(new int[]{readings[i], i}); 24 side[i] = 1; 25 highSize++; 26 } 27 while (lowSize > highSize + 1) { 28 while (expired[low.peek()[1]]) low.poll(); 29 int[] moved = low.poll(); 30 high.offer(moved); 31 side[moved[1]] = 1; 32 lowSize--; 33 highSize++; 34 } 35 while (highSize > lowSize) { 36 while (expired[high.peek()[1]]) high.poll(); 37 int[] moved = high.poll(); 38 low.offer(moved); 39 side[moved[1]] = 0; 40 highSize--; 41 lowSize++; 42 } 43 if (i >= k - 1) { 44 while (expired[low.peek()[1]]) low.poll(); 45 if (lowSize > highSize) { 46 answers.add(2 * low.peek()[0]); 47 } else { 48 while (expired[high.peek()[1]]) high.poll(); 49 answers.add(low.peek()[0] + high.peek()[0]); 50 } 51 } 52 } 53 return answers; 54 } 55}

Related Problems