Leetcode 239 滑動視窗最大值 與堆排序kotlin語言(超時)

狐狸愛吃臭豆腐發表於2021-01-05

Leetcode 239 滑動視窗最大值 與堆排序kotlin語言(超時)

首先看題目描述:

思路:

被慣性思維害死了。

一眼,哦最大的數字。要排序。再看提示,堆,滑動視窗。懂了 大根堆。

上來就直接一個大根堆,幸虧資料結構學得還行,不然直接碼 真碼不出來。

實現:

首先是堆得部分:

fun BuildMaxHeap(heap:IntArray, lenth:Int){//建立大根堆 順序表儲存 資訊內容儲存在1-n位 0號預留,用作處理

    for (i in lenth/2 downTo 1 ) {//自下而上反覆調整堆
        HeadAdjust( heap,i,lenth)
    }

}

fun HeapInsert(heap:IntArray, lenth:Int, num:Int){//往堆中插入一個數字,該題中堆的大小穩定,故直接給最後位,然後重新建堆即可
    heap[lenth] = num
    BuildMaxHeap( heap, lenth)

}

fun HeapOut(heap:IntArray, lenth:Int, num:Int){//從堆中刪除一個數字

    
    
    var templenth = lenth
    var which = lenth //它在哪個位置 如果是連結串列儲存的堆需要層次遍歷
    for(i in 1 until lenth){
        if(heap[i] == num){
            which = i
            break
        }
    }
    
    
    heap[which] = heap[lenth]//把被刪除數和最後的數字調換(但並不用管被刪除數
    
    templenth --

    for (j in templenth/2 downTo which) {//自下而上反覆調整堆,最前面穩定的大根堆不需要調整
        HeadAdjust( heap, j, templenth)
    }

    


}



fun HeadAdjust(heap:IntArray, k :Int, lenth:Int){//自上而下調整 將元素k為根的子樹進行調整

    heap[0] = heap[k] //0號元素暫存子樹的根節點

    var root = k

    var i = 2 * k

    while ( i <= lenth) {//沿著較大的子節點向下篩選
        if(i<lenth && heap[i] < heap[i+1]){
            i++       //取較大的子節點的下標
        }
        if(heap[0]>=heap[i])
            break;   //根節點比左右孩子都大 篩選結束   
        else{        //孩子節點更大
            heap[root] = heap[i] //交換 孩子節點和雙親節點
            heap[i] = heap[0]
            root = i   //修改root值,以便繼續向下篩選
        }
        i*=2
    }

    heap[0] = 0


}

具體大根堆得原理:

其實就是一個祖先節點比其後代節點要大的完全二叉樹。

建立大根堆的時候從下往上一一調整直到任何子樹調整為祖先節點都小於其後代節點。

每次插入都插在最後面,從下往上一一調整根的位置。

每次刪除都和最後的元素調換位置,從下往上直到要刪除的位置一一調整位置。

按照教材的說法,在建立過程中時間複雜度為O(n) 每次調整時間複雜度為O(h) 故在最好、最壞和平均情況下,堆排序的時間複雜度為O(log2n)

其次是利用大根堆實現功能:

fun maxSlidingWindow(nums: IntArray, k: Int): IntArray {

    val tempArray = IntArray(k)
    for (i in 0 until k) {
        tempArray[i] = nums[i]
    }
    val heap = intArrayOf(0)+tempArray
                                    //首先取陣列前k個資料送入待處理,第一位置為0用作調整堆的臨時存放
                                    //關於為什麼不生成一個新的臨時變數 而採取0號位有以下好處:
                                    //1.堆實際上是遵循了一定規則的完全二叉樹,採用順序表儲存對比連結串列可以加快執行速度。
                                    //  以1為開頭,則其左右孩子節點分別為2,3 對應以i為長輩節點,則其左右孩子節點分別為2*i與2*i+1
                                    //  減輕堆的處理負責度
                                    //2.充分利用順序表的隨機讀寫特性,不必額外每次控制生成一個變數,減輕了開銷

    BuildMaxHeap( heap, k)     //陣列生成堆

    val intarray = IntArray(nums.size-k+1)

    for( i in 0 .. nums.size-k-1){
        
        intarray[i] = heap[1]
        HeapOut(heap,k,nums[i])
        HeapInsert(heap,k,nums[i+k])
        
    }
    intarray[nums.size - k] = heap[1]
    return intarray

}

簡要過程:

就是維護大根堆,然後從大根堆讀出最大值

結果:

太糟糕了,第49個例項,要求我維護大小為5W的大根堆。直接超時

反思:

再仔細想想,任何排序都需要花費較長時間。大根堆的時間複雜度已經算是比較低的了。就思路方面絕對沒有問題。

回過頭來就題論題,本題其實並未要求維護一個有序陣列,即並不需要使用排序。況且數字的大小規定為[-10000,10000],這完全可以用hash來做這道題。

總結:

雖然沒有做出題目,但是運用到的資料結構內容也是比較好的。408如果出題不可能出成這樣的。感到惋惜,故留下這篇文章。

相關文章