Track the Running Twice-Median of a Live Price Feed
Solve this ProblemA trading desk receives a live feed of prices. You are given a log of events: record adds a price to the feed, and query asks for the median of every price recorded so far. Report each median doubled (for an odd count that is twice the middle price; for an even count it is the sum of the two middle prices) so the answers are always integers, and return them in order.
Re-sorting the entire history on every query gets slower as the feed grows. Keeping the smaller half of the prices in a max-heap and the larger half in a min-heap puts the median right at the two roots, so a query is instant and each new price only costs a heap insertion.
Test Case 1:
Test Case 2:
Test Case 3:
Constraints
- ◆
1 ≤ ops.length ≤ 100, and values.length equals ops.length - ◆
Each op is "record" (add values[i] to the feed) or "query" (report the current median, doubled) - ◆
-1000 ≤ values[i] ≤ 1000 for every "record"; the values entry of a "query" is ignored - ◆
At least one "record" appears before every "query" - ◆
The median of an even number of prices is the average of the two middle prices; report it doubled so the answer is always an integer
Try the Dry Run
Don't just read the solution — watch it execute, one step at a time.
Approach & Solutions
Brute Force — Re-Sort Every Recorded Price on Each Query
BruteStore every recorded price in a plain list. When a query arrives, copy the list, sort the copy, and read the middle: for n prices the doubled median is sorted[(n-1)/2] + sorted[n/2], which works for both odd and even n. It is easy to get right, but every single query sorts everything recorded so far from scratch — long feeds with frequent queries make this very slow.
O(q · n log n)O(n)1class Solution {
2 public List<Integer> runningTwiceMedian(String[] ops, int[] values) {
3 List<Integer> seen = new ArrayList<>();
4 List<Integer> answers = new ArrayList<>();
5 for (int i = 0; i < ops.length; i++) {
6 if (ops[i].equals("record")) {
7 seen.add(values[i]);
8 } else {
9 List<Integer> sorted = new ArrayList<>(seen);
10 Collections.sort(sorted);
11 int n = sorted.size();
12 answers.add(sorted.get((n - 1) / 2) + sorted.get(n / 2));
13 }
14 }
15 return answers;
16 }
17}Optimal — Two Heaps Split Around the Median
OptimalSplit the recorded prices into two halves and keep each half in a heap: a max-heap "low" holding the smaller half and a min-heap "high" holding the larger half. The invariant is that every price in low is ≤ every price in high, and low is either the same size as high or exactly one larger. Then the median sits at the roots: if low is bigger it is low's root (doubled), otherwise it is the average of the two roots (their sum, doubled). A new price goes into low if it is ≤ low's root, otherwise into high; if that leaves the sizes unbalanced, the root of the too-large heap is moved across. Every record costs O(log n) and every query costs O(1). (In the C solution the max-heap is a min-heap of negated values.)
O(n log n)O(n)1class Solution {
2 public List<Integer> runningTwiceMedian(String[] ops, int[] values) {
3 PriorityQueue<Integer> low = new PriorityQueue<>(Collections.reverseOrder());
4 PriorityQueue<Integer> high = new PriorityQueue<>();
5 List<Integer> answers = new ArrayList<>();
6 for (int i = 0; i < ops.length; i++) {
7 if (ops[i].equals("record")) {
8 if (low.isEmpty() || values[i] <= low.peek()) {
9 low.offer(values[i]);
10 } else {
11 high.offer(values[i]);
12 }
13 if (low.size() > high.size() + 1) {
14 high.offer(low.poll());
15 } else if (high.size() > low.size()) {
16 low.offer(high.poll());
17 }
18 } else if (low.size() > high.size()) {
19 answers.add(2 * low.peek());
20 } else {
21 answers.add(low.peek() + high.peek());
22 }
23 }
24 return answers;
25 }
26}