🧠
Balanced Two Heaps with Indexed Removal Using TreeMap / Balanced BST (Java) or Multiset (C++)
💡 This approach uses balanced BST structures to support efficient insertion, deletion, and median retrieval by index, avoiding lazy removal complexity.
Intuition
Maintain two balanced multisets or TreeMaps representing smaller and larger halves. Insert and remove elements directly by value and index, keeping sizes balanced to get median in O(log k).
Algorithm
- Use two balanced multisets (or TreeMaps) to store smaller and larger halves of the window.
- Insert first k elements, balancing sizes after each insertion.
- For each sliding step, insert new element and remove outgoing element directly.
- Rebalance multisets to maintain size property.
- Calculate median from the largest element in smaller half and smallest in larger half.
💡 This approach is easier to implement in languages with built-in balanced trees but requires understanding of these data structures.
import bisect
from typing import List
def max_sliding_window_median(nums: List[int], k: int) -> List[float]:
window = sorted(nums[:k])
result = []
def get_median():
if k % 2 == 1:
return float(window[k // 2])
else:
return (window[k // 2 - 1] + window[k // 2]) / 2
result.append(get_median())
for i in range(k, len(nums)):
# Remove nums[i-k]
idx = bisect.bisect_left(window, nums[i - k])
window.pop(idx)
# Insert nums[i]
bisect.insort_left(window, nums[i])
result.append(get_median())
return result
# Driver code
if __name__ == '__main__':
nums = [1,3,-1,-3,5,3,6,7]
k = 3
print(max_sliding_window_median(nums, k))
Line Notes
window = sorted(nums[:k])Initialize sorted window for first k elements
idx = bisect.bisect_left(window, nums[i - k])Find index of outgoing element to remove
window.pop(idx)Remove outgoing element from sorted window
bisect.insort_left(window, nums[i])Insert new element maintaining sorted order
import java.util.*;
public class SlidingWindowMedian {
public static List<Double> maxSlidingWindowMedian(int[] nums, int k) {
List<Double> result = new ArrayList<>();
TreeMap<Integer, Integer> window = new TreeMap<>();
for (int i = 0; i < k; i++) {
window.put(nums[i], window.getOrDefault(nums[i], 0) + 1);
}
result.add(getMedian(window, k));
for (int i = k; i < nums.length; i++) {
// Remove nums[i-k]
int oldNum = nums[i - k];
window.put(oldNum, window.get(oldNum) - 1);
if (window.get(oldNum) == 0) window.remove(oldNum);
// Add nums[i]
window.put(nums[i], window.getOrDefault(nums[i], 0) + 1);
result.add(getMedian(window, k));
}
return result;
}
private static double getMedian(TreeMap<Integer, Integer> window, int k) {
int mid1 = (k - 1) / 2, mid2 = k / 2;
int c = 0;
Integer median1 = null, median2 = null;
for (Map.Entry<Integer, Integer> entry : window.entrySet()) {
c += entry.getValue();
if (median1 == null && c > mid1) median1 = entry.getKey();
if (median2 == null && c > mid2) {
median2 = entry.getKey();
break;
}
}
if (k % 2 == 1) return (double) median2;
else return (median1 + median2) / 2.0;
}
public static void main(String[] args) {
int[] nums = {1,3,-1,-3,5,3,6,7};
int k = 3;
System.out.println(maxSlidingWindowMedian(nums, k));
}
}
Line Notes
TreeMap<Integer, Integer> window = new TreeMap<>();Use TreeMap to store counts of elements in window
window.put(nums[i], window.getOrDefault(nums[i], 0) + 1);Add element count to window
window.put(oldNum, window.get(oldNum) - 1);Remove element count from window
for (Map.Entry<Integer, Integer> entry : window.entrySet())Iterate to find median by cumulative counts
#include <iostream>
#include <vector>
#include <set>
using namespace std;
double getMedian(multiset<int>& window, int k) {
auto mid1 = next(window.begin(), (k - 1) / 2);
auto mid2 = next(window.begin(), k / 2);
if (k % 2 == 1) return (double)(*mid2);
else return ((*mid1) + (*mid2)) / 2.0;
}
vector<double> maxSlidingWindowMedian(vector<int>& nums, int k) {
vector<double> result;
multiset<int> window(nums.begin(), nums.begin() + k);
result.push_back(getMedian(window, k));
for (int i = k; i < (int)nums.size(); i++) {
window.erase(window.find(nums[i - k]));
window.insert(nums[i]);
result.push_back(getMedian(window, k));
}
return result;
}
int main() {
vector<int> nums = {1,3,-1,-3,5,3,6,7};
int k = 3;
vector<double> res = maxSlidingWindowMedian(nums, k);
for (double val : res) cout << val << " ";
cout << endl;
return 0;
}
Line Notes
multiset<int> window(nums.begin(), nums.begin() + k);Initialize multiset with first k elements
window.erase(window.find(nums[i - k]));Remove outgoing element from multiset
window.insert(nums[i]);Insert new element into multiset
auto mid2 = next(window.begin(), k / 2);Find iterator to median element
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return left;
}
function maxSlidingWindowMedian(nums, k) {
let window = nums.slice(0, k).sort((a,b) => a-b);
const result = [];
function getMedian() {
if (k % 2 === 1) return window[Math.floor(k/2)];
else return (window[k/2 - 1] + window[k/2]) / 2;
}
result.push(getMedian());
for (let i = k; i < nums.length; i++) {
let outIdx = binarySearch(window, nums[i - k]);
window.splice(outIdx, 1);
let inIdx = binarySearch(window, nums[i]);
window.splice(inIdx, 0, nums[i]);
result.push(getMedian());
}
return result;
}
// Test
const nums = [1,3,-1,-3,5,3,6,7];
const k = 3;
console.log(maxSlidingWindowMedian(nums, k));
Line Notes
function binarySearch(arr, target)Helper to find insertion/removal index in sorted array
window.splice(outIdx, 1);Remove outgoing element from window
window.splice(inIdx, 0, nums[i]);Insert new element maintaining sorted order
result.push(getMedian());Add median of current window to results
Insertion and removal in TreeMap or balanced BST costs O(log k), but median retrieval by iterating cumulative counts is O(k) per window, leading to O(n k) worst-case time. Space is O(k) for the window.
💡 This approach is simpler to implement in some languages but median retrieval can be slower due to iteration over counts.
Interview Verdict: Accepted
This approach is a good alternative when lazy removal is complex or unsupported.