Twice-the-Median of Every Window Along a Sensor Trace
Solve this ProblemA 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:
Test Case 2:
Test Case 3:
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
BruteFor 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.
O(n · k log k)O(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
OptimalKeep 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.
O(n log n)O(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}