The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle values.
For examples, if arr = [2,3,4], the median is 3.
For examples, if arr = [1,2,3,4], the median is (2 + 3) / 2 = 2.5.
You are given an integer array nums and an integer k. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the median array for each window in the original array. Answers within 10-5 of the actual value will be accepted.
Median of every window. Sorting each window is \(O(nk\log k)\). Heaps give the median but cannot delete an arbitrary outgoing value.
Two heaps: a max-heap for the lower half, a min-heap for the upper half, plus a lazy-deletion map. After insert or erase, only expired heap tops are popped, then the live sizes are rebalanced.
Lazy deletion turns an arbitrary erase into a top pop; live sizes are counted separately so the tops remain the median candidates.
We can use two priority queues (min-heap and max-heap) to maintain the elements in the current window. One priority queue stores the smaller half of the elements, and the other priority queue stores the larger half of the elements. This way, the median of the current window is either the average of the top elements of the two heaps or one of the top elements.
We design a class \(\textit{MedianFinder}\) to maintain the elements in the current window. This class includes the following methods:
add_num(num): Adds \(\textit{num}\) to the current window.
find_median(): Returns the median of the elements in the current window.
remove_num(num): Removes \(\textit{num}\) from the current window.
prune(pq): If the top element of the heap is in the lazy deletion dictionary \(\textit{delayed}\), it pops the top element from the heap and decrements its lazy deletion count. If the lazy deletion count of the element becomes zero, it removes the element from the lazy deletion dictionary.
rebalance(): If the number of elements in the smaller half exceeds the number of elements in the larger half by \(2\), it moves the top element of the larger half to the smaller half. If the number of elements in the smaller half is less than the number of elements in the larger half, it moves the top element of the larger half to the smaller half.
In the add_num(num) method, we first consider adding \(\textit{num}\) to the smaller half. If \(\textit{num}\) is greater than the top element of the larger half, we add \(\textit{num}\) to the larger half. Then we call the rebalance() method to ensure that the size difference between the two priority queues does not exceed \(1\).
In the remove_num(num) method, we increment the lazy deletion count of \(\textit{num}\). Then we compare \(\textit{num}\) with the top element of the smaller half. If \(\textit{num}\) is less than or equal to the top element of the smaller half, we update the size of the smaller half and call the prune() method to ensure that the top element of the smaller half is not in the lazy deletion dictionary. Otherwise, we update the size of the larger half and call the prune() method to ensure that the top element of the larger half is not in the lazy deletion dictionary.
In the find_median() method, if the current window size is odd, we return the top element of the smaller half; otherwise, we return the average of the top elements of the smaller half and the larger half.
In the prune(pq) method, if the top element of the heap is in the lazy deletion dictionary, it pops the top element from the heap and decrements its lazy deletion count. If the lazy deletion count of the element becomes zero, it removes the element from the lazy deletion dictionary.
In the rebalance() method, if the number of elements in the smaller half exceeds the number of elements in the larger half by \(2\), it moves the top element of the larger half to the smaller half. If the number of elements in the smaller half is less than the number of elements in the larger half, it moves the top element of the larger half to the smaller half.
The time complexity is \(O(n \times \log n)\), and the space complexity is \(O(n)\). Here, \(n\) is the length of the array \(\textit{nums}\).
Lazy heaps are long to implement. An ordered set inserts and deletes in \(O(\log k)\). The left set holds the lower half, the right the upper half: insert into the right, move its minimum left, and rebalance. When the window is full, read the ends and remove the outgoing value.
The code is shorter if an ordered-set type is available.
We can use two ordered sets to maintain the elements in the current window. The ordered set \(l\) stores the smaller half of the elements in the current window, and the ordered set \(r\) stores the larger half of the elements.
We traverse the array \(\textit{nums}\). For each element \(x\), we add it to the ordered set \(r\), then move the smallest element in the ordered set \(r\) to the ordered set \(l\). If the size of the ordered set \(l\) is greater than the size of the ordered set \(r\) by more than \(1\), we move the largest element in the ordered set \(l\) to the ordered set \(r\).
If the total number of elements in the current window is \(k\) and the size is odd, the maximum value in the ordered set \(l\) is the median. If the size of the current window is even, the average of the maximum value in the ordered set \(l\) and the minimum value in the ordered set \(r\) is the median. Then, we remove the leftmost element of the window and continue traversing the array.
The time complexity is \(O(n \log k)\), and the space complexity is \(O(k)\). Here, \(n\) is the length of the array \(\textit{nums}\).
functionmedianSlidingWindow(nums:number[],k:number):number[]{constl=newTreapMultiSet<number>((a,b)=>a-b);constr=newTreapMultiSet<number>((a,b)=>a-b);constn=nums.length;constans:number[]=[];for(leti=0;i<n;++i){r.add(nums[i]);l.add(r.shift()!);while(l.size-r.size>1){r.add(l.pop()!);}constj=i-k+1;if(j>=0){ans[j]=k%2?l.last()!:(l.last()!+r.first()!)/2;if(nums[j]<=l.last()!){l.delete(nums[j]);}else{r.delete(nums[j]);}}}returnans;}typeCompareFunction<T,Rextends'number'|'boolean'>=(a:T,b:T,)=>Rextends'number'?number:boolean;interfaceITreapMultiSet<T>extendsIterable<T>{add:(...value:T[])=>this;has:(value:T)=>boolean;delete:(value:T)=>void;bisectLeft:(value:T)=>number;bisectRight:(value:T)=>number;indexOf:(value:T)=>number;lastIndexOf:(value:T)=>number;at:(index:number)=>T|undefined;first:()=>T|undefined;last:()=>T|undefined;lower:(value:T)=>T|undefined;higher:(value:T)=>T|undefined;floor:(value:T)=>T|undefined;ceil:(value:T)=>T|undefined;shift:()=>T|undefined;pop:(index?:number)=>T|undefined;count:(value:T)=>number;keys:()=>IterableIterator<T>;values:()=>IterableIterator<T>;rvalues:()=>IterableIterator<T>;entries:()=>IterableIterator<[number,T]>;readonlysize:number;}classTreapNode<T=number>{value:T;count:number;size:number;priority:number;left:TreapNode<T>|null;right:TreapNode<T>|null;constructor(value:T){this.value=value;this.count=1;this.size=1;this.priority=Math.random();this.left=null;this.right=null;}staticgetSize(node:TreapNode<any>|null):number{returnnode?.size??0;}staticgetFac(node:TreapNode<any>|null):number{returnnode?.priority??0;}pushUp():void{lettmp=this.count;tmp+=TreapNode.getSize(this.left);tmp+=TreapNode.getSize(this.right);this.size=tmp;}rotateRight():TreapNode<T>{// eslint-disable-next-line @typescript-eslint/no-this-aliasletnode:TreapNode<T>=this;constleft=node.left;node.left=left?.right??null;left&&(left.right=node);left&&(node=left);node.right?.pushUp();node.pushUp();returnnode;}rotateLeft():TreapNode<T>{// eslint-disable-next-line @typescript-eslint/no-this-aliasletnode:TreapNode<T>=this;constright=node.right;node.right=right?.left??null;right&&(right.left=node);right&&(node=right);node.left?.pushUp();node.pushUp();returnnode;}}classTreapMultiSet<T=number>implementsITreapMultiSet<T>{privatereadonlyroot:TreapNode<T>;privatereadonlycompareFn:CompareFunction<T,'number'>;privatereadonlyleftBound:T;privatereadonlyrightBound:T;constructor(compareFn?:CompareFunction<T,'number'>);constructor(compareFn:CompareFunction<T,'number'>,leftBound:T,rightBound:T);constructor(compareFn:CompareFunction<T,any>=(a:any,b:any)=>a-b,leftBound:any=-Infinity,rightBound:any=Infinity,){this.root=newTreapNode<T>(rightBound);this.root.priority=Infinity;this.root.left=newTreapNode<T>(leftBound);this.root.left.priority=-Infinity;this.root.pushUp();this.leftBound=leftBound;this.rightBound=rightBound;this.compareFn=compareFn;}getsize():number{returnthis.root.size-2;}getheight():number{constgetHeight=(node:TreapNode<T>|null):number=>{if(node==null)return0;return1+Math.max(getHeight(node.left),getHeight(node.right));};returngetHeight(this.root);}/** * * @complexity `O(logn)` * @description Returns true if value is a member. */has(value:T):boolean{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):boolean=>{if(node==null)returnfalse;if(compare(node.value,value)===0)returntrue;if(compare(node.value,value)<0)returndfs(node.right,value);returndfs(node.left,value);};returndfs(this.root,value);}/** * * @complexity `O(logn)` * @description Add value to sorted set. */add(...values:T[]):this{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T,parent:TreapNode<T>,direction:'left'|'right',):void=>{if(node==null)return;if(compare(node.value,value)===0){node.count++;node.pushUp();}elseif(compare(node.value,value)>0){if(node.left){dfs(node.left,value,node,'left');}else{node.left=newTreapNode(value);node.pushUp();}if(TreapNode.getFac(node.left)>node.priority){parent[direction]=node.rotateRight();}}elseif(compare(node.value,value)<0){if(node.right){dfs(node.right,value,node,'right');}else{node.right=newTreapNode(value);node.pushUp();}if(TreapNode.getFac(node.right)>node.priority){parent[direction]=node.rotateLeft();}}parent.pushUp();};values.forEach(value=>dfs(this.root.left,value,this.root,'left'));returnthis;}/** * * @complexity `O(logn)` * @description Remove value from sorted set if it is a member. * If value is not a member, do nothing. */delete(value:T):void{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T,parent:TreapNode<T>,direction:'left'|'right',):void=>{if(node==null)return;if(compare(node.value,value)===0){if(node.count>1){node.count--;node?.pushUp();}elseif(node.left==null&&node.right==null){parent[direction]=null;}else{// 旋到根节点if(node.right==null||TreapNode.getFac(node.left)>TreapNode.getFac(node.right)){parent[direction]=node.rotateRight();dfs(parent[direction]?.right??null,value,parent[direction]!,'right');}else{parent[direction]=node.rotateLeft();dfs(parent[direction]?.left??null,value,parent[direction]!,'left');}}}elseif(compare(node.value,value)>0){dfs(node.left,value,node,'left');}elseif(compare(node.value,value)<0){dfs(node.right,value,node,'right');}parent?.pushUp();};dfs(this.root.left,value,this.root,'left');}/** * * @complexity `O(logn)` * @description Returns an index to insert value in the sorted set. * If the value is already present, the insertion point will be before (to the left of) any existing values. */bisectLeft(value:T):number{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):number=>{if(node==null)return0;if(compare(node.value,value)===0){returnTreapNode.getSize(node.left);}elseif(compare(node.value,value)>0){returndfs(node.left,value);}elseif(compare(node.value,value)<0){returndfs(node.right,value)+TreapNode.getSize(node.left)+node.count;}return0;};returndfs(this.root,value)-1;}/** * * @complexity `O(logn)` * @description Returns an index to insert value in the sorted set. * If the value is already present, the insertion point will be before (to the right of) any existing values. */bisectRight(value:T):number{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):number=>{if(node==null)return0;if(compare(node.value,value)===0){returnTreapNode.getSize(node.left)+node.count;}elseif(compare(node.value,value)>0){returndfs(node.left,value);}elseif(compare(node.value,value)<0){returndfs(node.right,value)+TreapNode.getSize(node.left)+node.count;}return0;};returndfs(this.root,value)-1;}/** * * @complexity `O(logn)` * @description Returns the index of the first occurrence of a value in the set, or -1 if it is not present. */indexOf(value:T):number{constcompare=this.compareFn;letisExist=false;constdfs=(node:TreapNode<T>|null,value:T):number=>{if(node==null)return0;if(compare(node.value,value)===0){isExist=true;returnTreapNode.getSize(node.left);}elseif(compare(node.value,value)>0){returndfs(node.left,value);}elseif(compare(node.value,value)<0){returndfs(node.right,value)+TreapNode.getSize(node.left)+node.count;}return0;};constres=dfs(this.root,value)-1;returnisExist?res:-1;}/** * * @complexity `O(logn)` * @description Returns the index of the last occurrence of a value in the set, or -1 if it is not present. */lastIndexOf(value:T):number{constcompare=this.compareFn;letisExist=false;constdfs=(node:TreapNode<T>|null,value:T):number=>{if(node==null)return0;if(compare(node.value,value)===0){isExist=true;returnTreapNode.getSize(node.left)+node.count-1;}elseif(compare(node.value,value)>0){returndfs(node.left,value);}elseif(compare(node.value,value)<0){returndfs(node.right,value)+TreapNode.getSize(node.left)+node.count;}return0;};constres=dfs(this.root,value)-1;returnisExist?res:-1;}/** * * @complexity `O(logn)` * @description Returns the item located at the specified index. * @param index The zero-based index of the desired code unit. A negative index will count back from the last item. */at(index:number):T|undefined{if(index<0)index+=this.size;if(index<0||index>=this.size)returnundefined;constdfs=(node:TreapNode<T>|null,rank:number):T|undefined=>{if(node==null)returnundefined;if(TreapNode.getSize(node.left)>=rank){returndfs(node.left,rank);}elseif(TreapNode.getSize(node.left)+node.count>=rank){returnnode.value;}else{returndfs(node.right,rank-TreapNode.getSize(node.left)-node.count);}};constres=dfs(this.root,index+2);return([this.leftBound,this.rightBound]asany[]).includes(res)?undefined:res;}/** * * @complexity `O(logn)` * @description Find and return the element less than `val`, return `undefined` if no such element found. */lower(value:T):T|undefined{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):T|undefined=>{if(node==null)returnundefined;if(compare(node.value,value)>=0)returndfs(node.left,value);consttmp=dfs(node.right,value);if(tmp==null||compare(node.value,tmp)>0){returnnode.value;}else{returntmp;}};constres=dfs(this.root,value)asany;returnres===this.leftBound?undefined:res;}/** * * @complexity `O(logn)` * @description Find and return the element greater than `val`, return `undefined` if no such element found. */higher(value:T):T|undefined{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):T|undefined=>{if(node==null)returnundefined;if(compare(node.value,value)<=0)returndfs(node.right,value);consttmp=dfs(node.left,value);if(tmp==null||compare(node.value,tmp)<0){returnnode.value;}else{returntmp;}};constres=dfs(this.root,value)asany;returnres===this.rightBound?undefined:res;}/** * * @complexity `O(logn)` * @description Find and return the element less than or equal to `val`, return `undefined` if no such element found. */floor(value:T):T|undefined{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):T|undefined=>{if(node==null)returnundefined;if(compare(node.value,value)===0)returnnode.value;if(compare(node.value,value)>=0)returndfs(node.left,value);consttmp=dfs(node.right,value);if(tmp==null||compare(node.value,tmp)>0){returnnode.value;}else{returntmp;}};constres=dfs(this.root,value)asany;returnres===this.leftBound?undefined:res;}/** * * @complexity `O(logn)` * @description Find and return the element greater than or equal to `val`, return `undefined` if no such element found. */ceil(value:T):T|undefined{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):T|undefined=>{if(node==null)returnundefined;if(compare(node.value,value)===0)returnnode.value;if(compare(node.value,value)<=0)returndfs(node.right,value);consttmp=dfs(node.left,value);if(tmp==null||compare(node.value,tmp)<0){returnnode.value;}else{returntmp;}};constres=dfs(this.root,value)asany;returnres===this.rightBound?undefined:res;}/** * @complexity `O(logn)` * @description * Returns the last element from set. * If the set is empty, undefined is returned. */first():T|undefined{constiter=this.inOrder();iter.next();constres=iter.next().value;returnres===this.rightBound?undefined:res;}/** * @complexity `O(logn)` * @description * Returns the last element from set. * If the set is empty, undefined is returned . */last():T|undefined{constiter=this.reverseInOrder();iter.next();constres=iter.next().value;returnres===this.leftBound?undefined:res;}/** * @complexity `O(logn)` * @description * Removes the first element from an set and returns it. * If the set is empty, undefined is returned and the set is not modified. */shift():T|undefined{constfirst=this.first();if(first===undefined)returnundefined;this.delete(first);returnfirst;}/** * @complexity `O(logn)` * @description * Removes the last element from an set and returns it. * If the set is empty, undefined is returned and the set is not modified. */pop(index?:number):T|undefined{if(index==null){constlast=this.last();if(last===undefined)returnundefined;this.delete(last);returnlast;}consttoDelete=this.at(index);if(toDelete==null)return;this.delete(toDelete);returntoDelete;}/** * * @complexity `O(logn)` * @description * Returns number of occurrences of value in the sorted set. */count(value:T):number{constcompare=this.compareFn;constdfs=(node:TreapNode<T>|null,value:T):number=>{if(node==null)return0;if(compare(node.value,value)===0)returnnode.count;if(compare(node.value,value)<0)returndfs(node.right,value);returndfs(node.left,value);};returndfs(this.root,value);}*[Symbol.iterator]():Generator<T,any,any>{yield*this.values();}/** * @description * Returns an iterable of keys in the set. */*keys():Generator<T,any,any>{yield*this.values();}/** * @description * Returns an iterable of values in the set. */*values():Generator<T,any,any>{constiter=this.inOrder();iter.next();conststeps=this.size;for(let_=0;_<steps;_++){yielditer.next().value;}}/** * @description * Returns a generator for reversed order traversing the set. */*rvalues():Generator<T,any,any>{constiter=this.reverseInOrder();iter.next();conststeps=this.size;for(let_=0;_<steps;_++){yielditer.next().value;}}/** * @description * Returns an iterable of key, value pairs for every entry in the set. */*entries():IterableIterator<[number,T]>{constiter=this.inOrder();iter.next();conststeps=this.size;for(leti=0;i<steps;i++){yield[i,iter.next().value];}}private*inOrder(root:TreapNode<T>|null=this.root):Generator<T,any,any>{if(root==null)return;yield*this.inOrder(root.left);constcount=root.count;for(let_=0;_<count;_++){yieldroot.value;}yield*this.inOrder(root.right);}private*reverseInOrder(root:TreapNode<T>|null=this.root):Generator<T,any,any>{if(root==null)return;yield*this.reverseInOrder(root.right);constcount=root.count;for(let_=0;_<count;_++){yieldroot.value;}yield*this.reverseInOrder(root.left);}}