[英]Trying to solve sliding window median problem in leetcode
我正在研究 LeetCode 代碼挑戰480。滑動窗口中位數:
給定一個整數數組 nums 和一個整數
k
。 有一個大小為k
的滑動窗口,它從數組的最左邊移動到最右邊。 您只能在窗口中看到k
數字。 每次滑動窗口向右移動一個位置。返回原始數組中每個窗口的中值數組。 實際值在 10 -5以內的答案將被接受。
我提交了我的代碼,但它在測試用例上失敗了。 我懷疑我的這部分代碼有問題:
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
這是完整的代碼:
class MaxHeap{
#array = [];
#size = 0;
#reference = [];
#map = new Map();
constructor(reference = []){
this.#reference = reference;
}
get size(){
return this.#size;
}
/* Debug */
get array(){
return this.#array;
}
get peak(){
return this.get(0);
}
get(index){
if(index === null || index < 0 || index >= this.#array.length) return null;
return this.#reference[this.#array[index]];
}
has(indexReference){
return this.#map.has(indexReference);
}
swap(indexA, indexB){
let temp = this.#map.get(this.#array[indexA]);
this.#map.set(this.#array[indexA], indexB);
this.#map.set(this.#array[indexB], temp);
[this.#array[indexA], this.#array[indexB]] = [this.#array[indexB], this.#array[indexA]];
}
sink(index){
let currentIndex = index;
let greterChild;
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 1 : 2*currentIndex + 2) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, greterChild);
currentIndex = greterChild;
}
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
push(...char){
if(char[0].constructor === Array) char = char.flat();
for(let i = 0; i < char.length; i++){
this.#array.push(char[i]);
this.#map.set(char[i], this.#array.length - 1)
this.bubble(this.#array.length - 1);
this.#size++;
}
}
pop(index = 0, fromReference = false){
const ret = (fromReference) ? index :this.#array[index];
if(fromReference) index = this.#map.get(index);
this.swap(index, this.#array.length - 1);
this.#map.delete(ret);
this.#array.pop();
this.sink(index);
this.#size--;
return ret;
}
}
class MinHeap extends MaxHeap{
constructor(reference = []){
super(reference);
}
get size(){
return super.size;
}
get peak(){
return super.peak;
}
/* Debug */
get array(){
return super.array;
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
sink(index){
let currentIndex = index;
let lesserChild;
while((this.get(lesserChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 2 : 2*currentIndex + 1) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, lesserChild);
currentIndex = lesserChild;
}
}
}
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
什么地方出了錯:
在問題的第 30 個測試用例(鏈接: https ://leetcode.com/problems/sliding-window-median/submissions/859041571/ )中,它解析為錯誤的答案,但是當我選擇其中一個解析為 a 的窗口時錯誤的答案它給了我一個正確的答案。 我目前感到困惑,因為其中兩個堆相當平衡(因為一個堆不超過一個元素)並且我已經測試了我的堆,兩者似乎都工作得很好。 如果有人幫助我,那將非常有幫助。
鏈接到我關注的 SO 問題:
你的堆實現存在這些問題:
當給出超出范圍的索引時, get
函數將返回null
,這意味着您的sink
方法中的while
條件有時會選擇一個不存在的孩子(當只有一個孩子時)。 請注意,與null
的數值比較會將null
視為 0,並且根據您與之比較的值的符號可以給出 false 或 true。
例如,您的代碼由於這個原因未能通過此測試用例:
nums=[1,2,3,4] k=4
您可以通過返回undefined
而不是null
來解決這個問題。 然后還要確保比較運算符的假邊是帶 +1 的那一邊(選擇左邊的孩子),而真邊取另一個孩子。
當第二個參數為true
時, pop
方法不保證恢復堆屬性。 它負責下沉給定索引處的值,但不考慮該值實際上應該冒泡的情況!
例如,您的代碼由於這個原因未能通過此測試用例:
nums=[10,6,5,2,3,0,8,1,4,12,7,13,11,9] k=11
這是一個簡化的示例,其中我描述了一個具有引用值的最小堆:
5 / \ 8 6 / \ / 10 12 7
如果要刪除值為 10 的節點,交換操作將給出這個最小堆(這是正確的):
5 / \ 8 6 / \ / 7 12 10
然后您的代碼在該節點上調用值為 7 的sink
。很明顯,這里沒有任何東西可以下沉,而是 7 應該冒泡並與 8 交換。您的代碼必須預見這兩種情況:篩選或冒泡。
如果您在堆實現中解決了這兩個問題,它就會起作用。
我在這里提供您必須進行的文字更改:
在get
方法中,將return null
替換為return undefined
(或省略顯式值)
在MaxHeap
sink
方法中,交換比較器表達式,替換:
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2)? 2*currentIndex + 1: 2*currentIndex + 2)?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
和:
while((this.get(greterChild = this.get(2*currentIndex+1) <= this.get(2*currentIndex + 2)? 2*currentIndex + 2: 2*currentIndex + 1)?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
在pop
方法中,替換:
this.sink(index);
和:
this.sink(index); this.bubble(index);
(您也可以先檢查兩者中的哪一個是需要的,但是只調用這兩個方法並沒有什么壞處)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.