數(shù)據(jù)結(jié)構(gòu)與算法(4)——優(yōu)先隊列和堆

前言:題圖無關(guān)毯欣,接下來開始簡單學習學習優(yōu)先隊列和堆的相關(guān)數(shù)據(jù)結(jié)構(gòu)的知識白粉;

前序文章:

什么是優(yōu)先隊列迈窟?

聽這個名字就能知道,優(yōu)先隊列也是一種隊列,只不過不同的是缅茉,優(yōu)先隊列的出隊順序是按照優(yōu)先級來的;在有些情況下湘换,可能需要找到元素集合中的最小或者最大元素宾舅,可以利用優(yōu)先隊列ADT來完成操作,優(yōu)先隊列ADT是一種數(shù)據(jù)結(jié)構(gòu)彩倚,它支持插入和刪除最小值操作(返回并刪除最小元素)或刪除最大值操作(返回并刪除最大元素)筹我;

這些操作等價于隊列的enQueuedeQueue操作,區(qū)別在于帆离,對于優(yōu)先隊列蔬蕊,元素進入隊列的順序可能與其被操作的順序不同,作業(yè)調(diào)度是優(yōu)先隊列的一個應用實例哥谷,它根據(jù)優(yōu)先級的高低而不是先到先服務的方式來進行調(diào)度岸夯;

如果最小鍵值元素擁有最高的優(yōu)先級,那么這種優(yōu)先隊列叫作升序優(yōu)先隊列(即總是先刪除最小的元素)们妥,類似的猜扮,如果最大鍵值元素擁有最高的優(yōu)先級,那么這種優(yōu)先隊列叫作降序優(yōu)先隊列(即總是先刪除最大的元素)监婶;由于這兩種類型時對稱的旅赢,所以只需要關(guān)注其中一種,如升序優(yōu)先隊列惑惶;

優(yōu)先隊列ADT

下面操作組成了優(yōu)先隊列的一個ADT煮盼;

1.優(yōu)先隊列的主要操作
優(yōu)先隊列是元素的容器,每個元素有一個相關(guān)的鍵值带污;

  • insert(key, data):插入鍵值為key的數(shù)據(jù)到優(yōu)先隊列中僵控,元素以其key進行排序;
  • deleteMin/deleteMax:刪除并返回最小/最大鍵值的元素鱼冀;
  • getMinimum/getMaximum:返回最小/最大劍指的元素报破,但不刪除它悠就;

2.優(yōu)先隊列的輔助操作

  • 第k最小/第k最大:返回優(yōu)先隊列中鍵值為第k個最小/最大的元素;
  • 大蟹豪印(size):返回優(yōu)先隊列中的元素個數(shù)理卑;
  • 堆排序(Heap Sort):基于鍵值的優(yōu)先級將優(yōu)先隊列中的元素進行排序;

優(yōu)先隊列的應用

  • 數(shù)據(jù)壓縮:赫夫曼編碼算法蔽氨;
  • 最短路徑算法:Dijkstra算法藐唠;
  • 最小生成樹算法:Prim算法;
  • 事件驅(qū)動仿真:顧客排隊算法鹉究;
  • 選擇問題:查找第k個最小元素宇立;
  • 等等等等....

優(yōu)先隊列的實現(xiàn)比較

實現(xiàn) 插入 刪除 尋找最小值
無序數(shù)組 1 n n
無序鏈表 1 n n
有序數(shù)組 n 1 1
有序鏈表 n 1 1
二叉搜索樹 logn(平均) logn(平均) logn(平均)
平衡二叉搜索樹 logn logn logn
二叉堆 logn logn 1

堆和二叉堆

什么是堆

堆是一顆具有特定性質(zhì)的二叉樹,堆的基本要求就是堆中所有結(jié)點的值必須大于或等于(或小于或等于)其孩子結(jié)點的值自赔,這也稱為堆的性質(zhì)妈嘹;堆還有另一個性質(zhì),就是當 h > 0 時绍妨,所有葉子結(jié)點都處于第 h 或 h - 1 層(其中 h 為樹的高度润脸,完全二叉樹),也就是說他去,堆應該是一顆完全二叉樹毙驯;

在下面的例子中,左邊的樹為堆(每個元素都大于其孩子結(jié)點的值)灾测,而右邊的樹不是堆(因為5大于其孩子結(jié)點2)

二叉堆

在二叉堆中爆价,每個結(jié)點最多有兩個孩子結(jié)點,在實際應用中媳搪,二叉堆已經(jīng)足夠滿足需求铭段,因此接下來主要討論二叉最小堆和二叉最大堆;

堆的表示:在描述堆的操作前秦爆,首先來看堆是怎樣表示的序愚,一種可能的方法就是使用數(shù)組,因為堆在形式上是一顆完全二叉樹等限,用數(shù)組來存儲它不會浪費任何空間展运,例如下圖:

用數(shù)組來表示堆不僅不會浪費空間還具有一定的優(yōu)勢:

  • 每個結(jié)點的左孩子為下標i的2倍:left child(i) = i * 2;每個結(jié)點的右孩子為下標i的2倍加1:right child(i) = i * 2 + 1
  • 每個結(jié)點的父親結(jié)點為下標的二分之一:parent(i) = i / 2精刷,注意這里是整數(shù)除,2和3除以2都為1蔗候,大家可以驗證一下怒允;
  • 注意:這里是把下標為0的地方空出來了的,主要是為了方便理解锈遥,如果0不空出來只需要在計算的時候把i值往右偏移一個位置就行了(也就是加1纫事,大家可以試試勘畔,下面的演示也采取這樣的方式);

二叉堆的相關(guān)操作

堆的基本結(jié)構(gòu)

public class MaxHeap<E extends Comparable<E>> {
    private Array<E> data;
    public MaxHeap(int capacity){ data = new Array<>(capacity); }
    public MaxHeap(){ data = new Array<>(); }
    // 返回堆中的元素個數(shù)
    public int size(){ return data.getSize(); }
    // 返回一個布爾值, 表示堆中是否為空
    public boolean isEmpty(){ return data.isEmpty(); }
    // 返回完全二叉樹的數(shù)組表示中丽惶,一個索引所表示的元素的父親節(jié)點的索引
    private int parent(int index){
        if(index == 0)
            throw new IllegalArgumentException("index-0 doesn't have parent.");
        return (index - 1) / 2;
    }
    // 返回完全二叉樹的數(shù)組表示中炫七,一個索引所表示的元素的左孩子節(jié)點的索引
    private int leftChild(int index){ return index * 2 + 1; }
    // 返回完全二叉樹的數(shù)組表示中,一個索引所表示的元素的右孩子節(jié)點的索引
    private int rightChild(int index){ return index * 2 + 2; }
}

向堆中添加元素和Sift Up

當插入一個元素到堆中時钾唬,它可能不滿足堆的性質(zhì)万哪,在這種情況下,需要調(diào)整堆中元素的位置使之重新變成堆抡秆,這個過程稱為堆化(heapifying)奕巍;在最大堆中,要堆化一個元素儒士,需要找到它的父親結(jié)點的止,如果不滿足堆的基本性質(zhì)則交換兩個元素的位置,重復該過程直到每個結(jié)點都滿足堆的性質(zhì)為止着撩,下面我們來模擬一下這個過程:

下面我們在該堆中插入一個新的元素26:

我們通過索引(上面的公式)可以很容易地找到新插入元素的父親結(jié)點诅福,然后比較它們的大小,如果新元素更大則交換兩個元素的位置拖叙,這個操作就相當于把該元素上浮了一下:

重復該操作直到26到了一個滿足堆條件的位置氓润,此時就完成了插入的操作:

對應的代碼如下:

// 向堆中添加元素
public void add(E e){
    data.addLast(e);
    siftUp(data.getSize() - 1);
}

private void siftUp(int k){

    while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
        data.swap(k, parent(k));
        k = parent(k);
    }
}

取出堆中的最大元素和Sift Down

如果理解了上述的過程,那么取出堆中的最大元素(堆頂元素)將變得容易憋沿,不過這里運用到一個小技巧旺芽,就是用最后一個元素替換掉棧頂元素,然后把最后一個元素刪除掉辐啄,這樣一來元素的總個數(shù)也滿足條件采章,然后只需要把棧頂元素依次往下調(diào)整就好了,這個操作就叫做Sift Down(下沉):

用最后元素替換掉棧頂元素壶辜,然后刪除最后一個元素:

然后比較其孩子結(jié)點的大忻踔邸:

如果不滿足堆的條件,那么就跟孩子結(jié)點中較大的一個交換位置:

重復該步驟砸民,直到16到達合適的位置:

完成取出最大元素的操作:

對應的代碼如下:

// 看堆中的最大元素
public E findMax(){
    if(data.getSize() == 0)
        throw new IllegalArgumentException("Can not findMax when heap is empty.");
    return data.get(0);
}

// 取出堆中最大元素
public E extractMax(){

    E ret = findMax();

    data.swap(0, data.getSize() - 1);
    data.removeLast();
    siftDown(0);

    return ret;
}

private void siftDown(int k){

    while(leftChild(k) < data.getSize()){
        int j = leftChild(k); // 在此輪循環(huán)中,data[k]和data[j]交換位置
        if( j + 1 < data.getSize() &&
                data.get(j + 1).compareTo(data.get(j)) > 0 )
            j ++;
        // data[j] 是 leftChild 和 rightChild 中的最大值

        if(data.get(k).compareTo(data.get(j)) >= 0 )
            break;

        data.swap(k, j);
        k = j;
    }
}

Replace 和 Heapify

Replace這個操作其實就是取出堆中最大的元素之后再新插入一個元素抵怎,常規(guī)的做法是取出最大元素之后,再利用上面的插入新元素的操作對堆進行Sift Up操作岭参,但是這里有一個小技巧就是直接使用新元素替換掉堆頂元素反惕,之后再進行Sift Down操作,這樣就把兩次O(logn)的操作變成了一次O(logn):

// 取出堆中的最大元素演侯,并且替換成元素e
public E replace(E e){

    E ret = findMax();
    data.set(0, e);
    siftDown(0);
    return ret;
}

Heapify翻譯過來就是堆化的意思姿染,就是將任意數(shù)組整理成堆的形狀,通常的做法是遍歷數(shù)組從0開始添加創(chuàng)建一個新的堆秒际,但是這里存在一個小技巧就是把當前數(shù)組就看做是一個完全二叉樹悬赏,然后從最后一個非葉子結(jié)點開始進行Sift Down操作就可以了狡汉,最后一個非葉子結(jié)點也很好找,就是最后一個結(jié)點的父親結(jié)點闽颇,大家可以驗證一下:

從22這個節(jié)點開始盾戴,依次開始Sift Down操作:

重復該過程直到堆頂元素:

完成堆化操作:

將n個元素逐個插入到一個空堆中,算法復雜度是O(nlogn)兵多,而heapify的過程尖啡,算法復雜度為O(n),這是有一個質(zhì)的飛躍的中鼠,下面是代碼:

public MaxHeap(E[] arr){
    data = new Array<>(arr);
    for(int i = parent(arr.length - 1) ; i >= 0 ; i --)
        siftDown(i);
}

基于堆的優(yōu)先隊列

首先我們的隊列仍然需要繼承我們之前將隊列時候聲明的哪個接口Queue可婶,然后實現(xiàn)這個接口中的方法就可以了,之類簡單寫一下:

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){ maxHeap = new MaxHeap<>(); }
    @Override
    public int getSize(){ return maxHeap.size(); }
    @Override
    public boolean isEmpty(){ return maxHeap.isEmpty(); }
    @Override
    public E getFront(){ return maxHeap.findMax(); }
    @Override
    public void enqueue(E e){ maxHeap.add(e); }
    @Override
    public E dequeue(){ return maxHeap.extractMax(); }
}

Java中的PriorityQueue

在Java中也實現(xiàn)了自己的優(yōu)先隊列java.util.PriorityQueue援雇,與我們自己寫的不同之處在于矛渴,Java中內(nèi)置的為最小堆除破,然后就是一些函數(shù)名不一樣旁钧,底層還是維護了一個Object類型的數(shù)組,大家可以戳戳看有什么不同癞揉,另外如果想要把最小堆變成最大堆可以給PriorityQueue傳入自己的比較器筐赔,例如:

// 默認為最小堆
PriorityQueue<Integer> pq = new PriorityQueue<>();

pq.add(5);
pq.add(2);
pq.add(1);
pq.add(10);
pq.add(3);

while (!pq.isEmpty()) {
    System.out.println(pq.poll() + ", ");
}
System.out.println();
System.out.println("————————————————————————");

// 使用Lambda表達式傳入自己的比較器轉(zhuǎn)換成最大堆
PriorityQueue<Integer> pq2 = new PriorityQueue<>((a, b) -> b - a);
pq2.add(5);
pq2.add(2);
pq2.add(1);
pq2.add(10);
pq2.add(3);

while (!pq2.isEmpty()) {
    System.out.println(pq2.poll() + ", ");
}

LeetCode相關(guān)題目整理

23. 合并K個排序鏈表

參考答案:(85ms)

public ListNode mergeKLists(ListNode[] lists) {
    if (lists == null || lists.length == 0) return null;

    PriorityQueue<ListNode> q = new PriorityQueue<>(Comparator.comparing(node -> node.val));
    for (int i = 0; i < lists.length; i++) {
        if (lists[i] != null) {
            q.add(lists[i]);
        }
    }

    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (!q.isEmpty()) {
        tail.next = q.poll();
        tail = tail.next;
        if (tail.next != null) {
            q.add(tail.next);
        }
    }

    return dummy.next;
}

215. 數(shù)組中的第K個最大元素

我的答案:(75ms)

public int findKthLargest(int[] nums, int k) {

    // 正確性判斷
    if (0 == nums.length || null == nums || k <= 0 || k > nums.length) {
        return -1;
    }

    // 構(gòu)造優(yōu)先隊列,默認為最小堆,傳入自定義的比較器轉(zhuǎn)換成最大堆
    PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
    for (Integer num : nums) {
        pq.add(num);
    }
    for (int i = 0; i < k - 1; i++) {
        pq.remove();
    }
    return pq.peek();
}

參考答案:(5ms)

public int findKthLargest(int[] nums, int k) {
    if (nums.length == 1) {
        return nums[0];
    }

    int max = nums[0];
    int min = nums[0];

    for (int i : nums) {
        max = i > max ? i : max;
        min = i < min ? i : min;
    }

    int[] arrs = new int[max - min + 1];

    for (int i : nums) {
        arrs[max - i]++;
    }

    int pos = 0;
    for (int i = 0; i < arrs.length; i++) {
        pos += arrs[i];
        if (pos >= k) {
            return max - i;
        }
    }

    return nums[0];
}

還看到一個簡單粗暴的铣猩,也是服了:(4ms)

public int findKthLargest(int[] nums, int k) {
    Arrays.sort(nums);
    return nums[nums.length - k];
}

而且我隨機生成了一個100萬數(shù)據(jù)的隨機數(shù)組,來測試這個簡單粗暴的方法的效率茴丰,發(fā)現(xiàn)當數(shù)據(jù)量上去之后达皿,排序這個操作變得繁瑣,我自己測試的時候贿肩,上面三個方法峦椰,第三個大概比第一個(我自己寫的方法)多花僅4倍的時間;

239. 滑動窗口最大值(類似劍指Offer面試題59)

參考答案:(88ms)

public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums == null || k <= 0) return new int[0];
    int[] res = new int[nums.length - k + 1];
    ArrayDeque<Integer> deque = new ArrayDeque<Integer>();

    int index = 0;
    for (int i = 0; i < nums.length; i++) {
        while (!deque.isEmpty() && deque.peek() < i - k + 1) // Ensure deque's size doesn't exceed k
            deque.poll();

        // Remove numbers smaller than a[i] from right(a[i-1]) to left, to make the first number in the deque the largest one in the window      
        while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i])
            deque.pollLast();

        deque.offer(i);// Offer the current index to the deque's tail

        if (i >= k - 1)// Starts recording when i is big enough to make the window has k elements 
            res[index++] = nums[deque.peek()];
    }
    return res;
}

參考答案2:(9ms)

public int[] maxSlidingWindow(int[] nums, int k) {
/*
思想:依次遍歷數(shù)組汰规,有效范圍在長度k內(nèi)尋找當前最大值汤功,在用result數(shù)組來依次存儲當前長度K內(nèi)的最大值;
     若在當前輪中出現(xiàn)新增的nums[end]大于curMax,直接替換即可溜哮;
     如果當前輪curMax不是新增的nums[end]滔金,在新的范圍內(nèi)重置curMax.
*/
    if (nums.length == 0 || k <= 0)
        return new int[0];
    int curMax = Integer.MIN_VALUE;
    for (int i = 0; i < k; i++) {
        if (nums[i] > curMax)
            curMax = nums[i];
    }
    int[] ans = new int[nums.length - k + 1];
    ans[0] = curMax;

    for (int start = 1; start + k - 1 < nums.length; start++) {
        int end = start + k - 1;
        if (nums[end] > curMax)
            curMax = nums[end];
        else if (nums[start - 1] == curMax) {//新增的不大于curMax,新范圍內(nèi)重置
            curMax = Integer.MIN_VALUE;
            for (int i = start; i <= end; i++) {
                if (nums[i] > curMax)
                    curMax = nums[i];
            }
        }
        ans[start] = curMax;
    }
    return ans;
}

264. 丑數(shù) II(劍指Offer面試題49)

參考答案:(7ms)

public int nthUglyNumber(int n) {
    // 正確性判斷
    if (n < 1 || n > 1690) {
        return -1;
    }
    int[] ugly = new int[n];
    ugly[0] = 1;
    int index2 = 0, index3 = 0, index5 = 0;
    int factor2 = 2, factor3 = 3, factor5 = 5;
    for (int i = 1; i < n; i++) {
        int min = Math.min(Math.min(factor2, factor3), factor5);
        ugly[i] = min;
        if (factor2 == min)
            factor2 = 2 * ugly[++index2];
        if (factor3 == min)
            factor3 = 3 * ugly[++index3];
        if (factor5 == min)
            factor5 = 5 * ugly[++index5];
    }
    return ugly[n - 1];
}

如果采用逐個判斷每個整數(shù)是不是丑數(shù)的解法茂嗓,直觀但不夠高效餐茵,所以我們就需要換一種思路,我的第一反應就是這其中一定有什么規(guī)律述吸,但是嘗試著找了一下忿族,沒找到...看了看答案才幡然醒悟,前面提到的算法之所以效率低,很大程度上是因為不管一個數(shù)是不是丑數(shù)肠阱,我們都要對它進行計算,接下來我們試著找到一種只計算丑數(shù)的方法朴读,而不在非丑數(shù)的整數(shù)上花費時間屹徘,根據(jù)丑數(shù)的定義,丑數(shù)應該是另一個丑數(shù)乘以2衅金、3或者5的結(jié)果(1除外)噪伊,因此,我們可以創(chuàng)建一個數(shù)組氮唯,里面的數(shù)字是排好序的丑數(shù)鉴吹,每個丑數(shù)都是前面的丑數(shù)乘以2、3或者5得到的惩琉,也就是上面的算法了..

295.數(shù)據(jù)流的中位數(shù)(劍指Offer面試題41)

參考答案:(219ms)

public class MedianFinder {

    PriorityQueue<Integer> maxHeap;
    PriorityQueue<Integer> minHeap;

    /**
     * initialize your data structure here.
     */
    public MedianFinder() {
        maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        minHeap = new PriorityQueue<>();
    }

    public void addNum(int num) {
        maxHeap.add(num);
        minHeap.add(maxHeap.poll());
        if (minHeap.size() - maxHeap.size() > 0) {
            maxHeap.add(minHeap.poll());
        }
    }

    public double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.peek() + minHeap.peek()) / 2.0;
        } else {
            return maxHeap.peek();
        }
    }
}

思路:這道題的實現(xiàn)思路有很多豆励,比如我們可以在插入的時候就將每個元素插入到正確的位置上,這樣返回中位數(shù)的時候就會是一個O(1)的操作瞒渠,下面列舉一張表來說明不同實現(xiàn)的復雜度具體是多少:

數(shù)據(jù)結(jié)構(gòu) 插入的時間復雜度 得到中位數(shù)的時間復雜度
沒有排序的數(shù)組 O(1) O(n)
排序的數(shù)組 O(n) O(1)
排序的鏈表 O(n) O(1)
二叉搜索樹 平均O(logn)良蒸,最差O(n) 平均O(logn),最差O(n)
AVL樹 O(logn) O(logn)
最大堆和最小堆 O(logn) O(logn)

AVL樹是一種很高效的數(shù)據(jù)結(jié)構(gòu)伍玖,但是在大多數(shù)的語言中都沒有現(xiàn)成的實現(xiàn)嫩痰,所以考慮用最大堆和最小堆,對于一個已經(jīng)排好序的數(shù)據(jù)容器窍箍,我們可以從中間分開分成兩個部分串纺,其中拿P1指向左半部分最大的元素,拿P2指向有半部分最小的元素椰棘,如果能夠保證數(shù)據(jù)容器左邊的數(shù)據(jù)都小于右邊的數(shù)據(jù)纺棺,那么即使左、右兩邊內(nèi)部的數(shù)據(jù)沒有排序晰搀,我們?nèi)匀豢梢愿鶕?jù)左邊最大的數(shù)和右邊最大的數(shù)得到中位數(shù):



如何快速從一個數(shù)據(jù)容器中找出最大數(shù)呢五辽?我們可以使用最大堆來實現(xiàn)這個數(shù)據(jù)容器,因為堆頂?shù)脑鼐褪亲畲蟮脑赝馑。煌瑯游覀兛梢允褂米钚《褋砜焖僬页鲆粋€數(shù)據(jù)容器中最小數(shù)杆逗。因此按照這個思路我們就可以使用一個最大堆實現(xiàn)左邊的數(shù)據(jù)容器,使用一個最小堆實現(xiàn)右邊的數(shù)據(jù)容器鳞疲,但是需要注意的是這兩個容器的大小差值不能超過1罪郊;

347. 前K個高頻元素(類似劍指Offer面試題40)

參考答案:(131ms)

public List<Integer> topKFrequent(int[] nums, int k) {
    TreeMap<Integer, Integer> map = new TreeMap<>();
    // 保存頻率
    for (int num : nums) {
        if (map.containsKey(num)) {
            map.put(num, map.get(num) + 1);
        } else {
            map.put(num, 1);
        }
    }

    PriorityQueue<Integer> pq = new PriorityQueue<>(Comparator.comparingInt(map::get));
    for (int key : map.keySet()) {
        if (pq.size() < k) {
            pq.add(key);
        } else if (map.get(key) > map.get(pq.peek())) {
            pq.remove();
            pq.add(key);
        }
    }

    LinkedList<Integer> res = new LinkedList<>();
    while (!pq.isEmpty()) {
        res.add(pq.remove());
    }
    return res;
}

692. 前K個高頻單詞

參考答案:(72ms)

public List<String> topKFrequent(String[] words, int k) {
    Map<String, Integer> count = new HashMap();
    for (String word: words) {
        count.put(word, count.getOrDefault(word, 0) + 1);
    }
    List<String> candidates = new ArrayList(count.keySet());
    Collections.sort(candidates, (w1, w2) -> count.get(w1).equals(count.get(w2)) ?
            w1.compareTo(w2) : count.get(w2) - count.get(w1));

    return candidates.subList(0, k);
}

這道題類似于上面的第347題,但是問題出在返回的順序上尚洽,需要自己來定義一個比較器來排序..然后也學到一個寫法悔橄,就是上面的第一個for循環(huán)里,getOrDefault()方法,get√..

參考答案2:(91ms)

public List<String> topKFrequent(String[] words, int k) {
    Map<String, Integer> count = new HashMap();
    for (String word: words) {
        count.put(word, count.getOrDefault(word, 0) + 1);
    }
    PriorityQueue<String> heap = new PriorityQueue<String>(
            (w1, w2) -> count.get(w1).equals(count.get(w2)) ?
                    w2.compareTo(w1) : count.get(w1) - count.get(w2) );

    for (String word: count.keySet()) {
        heap.offer(word);
        if (heap.size() > k) heap.poll();
    }

    List<String> ans = new ArrayList();
    while (!heap.isEmpty()) ans.add(heap.poll());
    Collections.reverse(ans);
    return ans;
}

這個解法就有點兒類似于上面的347題癣疟,其實是大同小異挣柬,就是自己不會靈活使用比較器而已,學習到了學習到了√...


簡單總結(jié)

今天算是很有收獲的一天睛挚,因為這兩種數(shù)據(jù)結(jié)構(gòu)都是自己特別不熟悉的邪蛔,特別是在刷了一些LeetCode相關(guān)題目之后,對這兩種數(shù)據(jù)有了很不一樣的認識扎狱,特別是堆的應用侧到,這是一種特別適合用來找第k小/大的特殊的數(shù)據(jù)結(jié)構(gòu),并且在Java中居然有直接的實現(xiàn)淤击,這可太棒了匠抗,而且今天的效率還算挺高的,滿足污抬;

歡迎轉(zhuǎn)載汞贸,轉(zhuǎn)載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學習之路以及各種Java學習資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壕吹,一起剝皮案震驚了整個濱河市著蛙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耳贬,老刑警劉巖踏堡,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咒劲,居然都是意外死亡顷蟆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門腐魂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帐偎,“玉大人,你說我怎么就攤上這事蛔屹∠鞣” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵兔毒,是天一觀的道長漫贞。 經(jīng)常有香客問我,道長育叁,這世上最難降的妖魔是什么迅脐? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮豪嗽,結(jié)果婚禮上谴蔑,老公的妹妹穿的比我還像新娘豌骏。我一直安慰自己,他們只是感情好隐锭,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布窃躲。 她就那樣靜靜地躺著,像睡著了一般钦睡。 火紅的嫁衣襯著肌膚如雪框舔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天赎婚,我揣著相機與錄音,去河邊找鬼樱溉。 笑死挣输,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的福贞。 我是一名探鬼主播撩嚼,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挖帘!你這毒婦竟也來了完丽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拇舀,失蹤者是張志新(化名)和其女友劉穎逻族,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骄崩,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡聘鳞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了要拂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抠璃。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脱惰,靈堂內(nèi)的尸體忽然破棺而出搏嗡,到底是詐尸還是另有隱情,我是刑警寧澤拉一,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布采盒,位于F島的核電站,受9級特大地震影響舅踪,放射性物質(zhì)發(fā)生泄漏纽甘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一抽碌、第九天 我趴在偏房一處隱蔽的房頂上張望悍赢。 院中可真熱鬧决瞳,春花似錦、人聲如沸左权。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏迟。三九已至屡贺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锌杀,已是汗流浹背甩栈。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糕再,地道東北人量没。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像突想,于是被迫代替她去往敵國和親殴蹄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容