https://leetcode.com/problems/sliding-window-median/
这题就是这一题的延伸版本:https://blog.csdn.net/chaochen1407/article/details/82688222
可以直接在这一题的原做法上加一个删除的操作即可。就是当这个window走过一个元素,就从两边的queue中某个删除掉就可以了。但是从priority queue里面删掉一个元素的过程是O(k),所以整个操作的复杂度就变成了O(nk)了,给出代码如下:
public double[] medianSlidingWindow(int[] nums, int k) {
PriorityQueue<Integer> minQueue = new PriorityQueue<>(k, Collections.reverseOrder());
PriorityQueue<Integer> maxQueue = new PriorityQueue<>();
double[] result = new double[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
if (minQueue.size() < maxQueue.size()) {
minQueue.add(nums[i]);
} else {
maxQueue.add(nums[i]);
}
if (!minQueue.isEmpty() && minQueue.peek() > maxQueue.peek()) {
int tmp = minQueue.poll();
minQueue.add(maxQueue.poll());
maxQueue.add(tmp);
}
if (i >= k - 1) {
if (k % 2 == 0) {
result[i - k + 1] = minQueue.peek() / 2.0 + maxQueue.peek() / 2.0;
} else {
result[i - k + 1] = (double)maxQueue.peek();
}
if (minQueue.isEmpty() || nums[i - k + 1] > minQueue.peek()) {
maxQueue.remove(nums[i - k + 1]);
} else {
minQueue.remove(nums[i - k + 1]);
}
}
}
return result;
}
其实这个操作的复杂度还是可以从O(nk)变回O(nlogk)的,用multiset替代priorityqueue即可。只是这个数据结构只有C++有,Java没有一个现成的数据结构,用TreeSet的话没办法处理相同的元素,所以我们只能自己弄一个MultiSet,其实用TreeMap做开发即可。
class MultiSortedSet {
TreeMap<Integer, Integer> map;
int size;
public MultiSortedSet() {
this.map = new TreeMap<>();
this.size = 0;
}
public int size() {
return this.size;
}
public int getLargest() {
if (this.size == 0) return 0;
return map.lastKey();
}
public int getSmallest() {
return this.size == 0 ? 0 : map.firstKey();
}
private int _removeEntry(Map.Entry<Integer, Integer> entry) {
Integer count = entry.getValue();
Integer val = entry.getKey();
count--;
this.size--;
if (count == 0) {
this.map.remove(val);
} else {
this.map.put(val, count);
}
return val;
}
private int _removeSideEntry(boolean isEnd) {
if (this.size == 0) return 0;
Map.Entry<Integer, Integer> entry = isEnd ? map.lastEntry() : map.firstEntry();
return this._removeEntry(entry);
}
public int removeLargest() {
return this._removeSideEntry(true);
}
public int removeSmallest() {
return this._removeSideEntry(false);
}
public int remove(int val) {
if (this.size == 0) return 0;
return this._removeEntry(this.map.ceilingEntry(val));
}
public void add(int val) {
this.size++;
this.map.put(val, this.map.getOrDefault(val, 0) + 1);
}
}
public double[] medianSlidingWindow(int[] nums, int k) {
MultiSortedSet leftSet = new MultiSortedSet();
MultiSortedSet rightSet = new MultiSortedSet();
double[] result = new double[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
if (rightSet.size() > leftSet.size()) {
leftSet.add(nums[i]);
} else {
rightSet.add(nums[i]);
}
if (i >= 1 && rightSet.getSmallest() < leftSet.getLargest()) {
int moveToRight = leftSet.removeLargest();
leftSet.add(rightSet.removeSmallest());
rightSet.add(moveToRight);
}
if (i >= k - 1) {
if (k % 2 == 0) {
result[i - k + 1] = leftSet.getLargest() / 2.0 + rightSet.getSmallest() / 2.0;
} else {
result[i - k + 1] = (double)rightSet.getSmallest();
}
int candidate = nums[i - k + 1];
if (candidate <= leftSet.getLargest()) {
leftSet.remove(candidate);
} else {
rightSet.remove(candidate);
}
}
}
return result;
}
只是要注意的是,虽然同样是O(logN)的操作,但是TreeSet的常数级别要比PriorityQueue要高。所以上面这个理论上O(nlogk)的代码跑起来居然比PriorityQueue的O(nk)的代码要慢。所以其实如果能够PriorityQueue完成的操作,还是不要用TreeSet或者TreeMap来做,有很多题都验证了后两者比前者要慢。