LeetCode解題記錄之隊列最大值

題目:

請定義一個隊列並實現函數 max_value 得到隊列裏的最大值,要求函數max_value、push_back 和 pop_front 的均攤時間複雜度都是O(1)。

若隊列爲空,pop_front 和 max_value 需要返回 -1

public class MaxQueue{

    public MaxQueue(){
        
    }

    public int max_value() {
        //do something
    }
    
    public void push_back(int value) {
        //do something
    }
    
    public int pop_front() {
        //do something
    }
}

以上是LeetCode給出的題目,理解一下就是說讓我們自己完成一個隊列類的構建。需要實現push_back方法將value插入隊列的尾部,pop_front方法將隊列頭取出,max_value將隊列中的最大值返回,但不將值移除隊列。

其中有一個時間複雜度的要求,要讓每一個方法的均攤時間複雜度變爲O(1)。這個是本題的唯一難點,其它的問題都是小意思啦。

爲了搞定這個題目我使用了雙List模式:

public class MaxQueue{

    private List<Integer> queue;//保存實際隊列數據
    private List<Integer> max;//保存隊列中的最大值

    public MaxQueue(){
        queue = new LinkedList<>();
        max = new LinkedList<>();
    }
    //...省略部分代碼
}

很多小夥伴可能會問爲什麼要用list來保存最大值,直接用int不就好了嗎?

可是事情不簡單吶,比如我將{5,4,3,2,1}依次加入隊列中,那麼此時調用max_value方法,應該返回多少呢?

小夥伴:此時應該返回 5 !

沒毛病,的確是返回5。但是如果我調用一次pop_front,然後在調用一次max_value方法,這個時候應該返回多少呢?

小夥伴:原隊列{5,4,3,2,1},出掉隊列頭就剩下{4,3,2,1},此時的最大值應該是4!

沒錯啦,如果此時你只是用int類型來存儲最大值,那麼在出隊列的時候,就會導致max_value失真呀。

小夥伴:那麼list怎麼樣保證最大值不會失真呢?

祕訣就在push_back方法中:

public class MaxQueue{
    //...省略部分代碼
    public void push_back(int value) {
        queue.add(value);
        while(max.size() > 0 && max.get(max.size()-1) < value){
            max.remove(max.size()-1);
        }
        max.add(value);
    }
    //...省略部分代碼
}

小夥伴:what?一個註釋都沒有,寫的這是啥意思?

莫着急,我舉幾個例子來說一下原理。比如:

{5,4}這個隊列中的最大值是多少?

小夥伴:5!

那通過pop_front方法取出隊列頭之後呢?

小夥伴:4!我知道了通過雙list的方法,對max集合進行排序就可以了,然後我們就實時知道最大值了!

是這樣沒錯,但是題目要求了時間複雜度爲O(1),任何一種排序算法的時間複雜度都不是O(1)能夠完成的

小夥伴:不通過排序算法的話,我們怎麼確定哪個值是最大值嗎?

當然可以,我們在每一個隊列入隊的時候進行一次比較就可以了

比如:queue={5} 此時max={5},因爲隊列中只有一個值,所以這個值肯定是最大值

隨後繼續添加數據入隊,queue={5,4} ,此時我們將max中的已有的值a與新入隊的值b做比較。

如果a > b,那麼max={a,b},如果a < b,那麼max={b},移除a。

根據這條公式,我們將queue中的值代入,得到max={5,4}。

小夥伴:你這樣的算法好像只能適用與隊列的值爲兩個的情況,如果隊列的值多了呢?

比如:queue={5,4,3,6,1},按照這個順序依次入隊、出隊。你應該怎麼處理?

OK,我們先按剛剛的規則走一遍,看看問題出在哪。

1)queue={5}max = {5}

2)queue={5,4}max={5,4}

3)queue={5,4,3}max={5,4,3}

4)queue={5,4,3,6}max={6}

5)...

小夥伴:你等會兒!!!怎麼到了第四步max突然就變成6了呢?而且還移除了其它值?

hhh,這就是隊列的規則啦。你想想queue的入隊規則是什麼?

小夥伴:每一個新加入的值都添加到隊尾

那麼出隊規則呢?

小夥伴:每一個出隊的值,都是當前隊列的隊頭,也就是最先加入隊列的值

沒錯,就是根據這個規則,在queue={5,4,3,6}的時候,無論5、4、3有沒有出棧,當前隊列的最大值都是6。

所以排在6前面且比6小的數,都可以直接忽略了,因爲不存在先讓6出隊,而5、4、3還留在隊內的情況。

小夥伴:那就是說,每添加一個新的數A入隊,就需要將A與max集合中已有的所有值相比較?

是這麼個意思,的確需要進行比較,但不是和所有的值比較。

比如說,queue={5,4,10,6,1,2,3,4},假設隊列按照這樣的順序,那麼max中應該怎麼存放最大值呢?

小夥伴:額,如果max中存放10 的話,當10出隊之後,剩下的數字6最大。如果存放6的話,當6出隊之後4最大...這有什麼規律嗎?

沒錯,存在一條規律。當queue={5,4,106,1,2,3,4},標紅的數字,就是max中要依次存放的值。

小夥伴:額,規律是什麼?

存放步驟講解:

1) queue={5}  max={5} 因爲5是唯一的數

2) queue={5,4}  max={5,4} 因爲在5出棧之後,4就是最大的數(唯一的數)

3) queue={5,4,10}  max={10}  因爲10比max中其它值都大,而且它還是最後一位

4) queue={5,4,10,6}  max={10,6} 10 還是max中最大的值,但它排在6前面,意思就是說,當10出隊之後,6就是最大的了

5) queue={5,4,10,6,1}  max={10,6,1} 原因和上面一樣,10和6依次出隊,1就是最大值了

6) queue={5,4,10,6,1,2}  max={10,6,2}  這裏的2入隊後,1不再是隊列的最後一位,當10、6出隊後,最大的數是2哦

7) queue={5,4,10,6,1,2,3}  max={10,6,3}  和上面一樣,3取代了2的位置,成爲了隊中最後的大數。

8) queue={5,4,10,6,1,2,3,4}  max={10,6,4}  到這裏4又取代了3

小夥伴:所以一個新數A入隊的時候都要和max中最後一個值B做比較?如果B > A,則將A添加至max的隊尾,如果B < A,就將B移除,然後再添加A。

沒錯。。。就是這麼個意思,這樣就能夠保證隊列max的隊頭,永遠是queue中的最大值。

//完整代碼
public class MaxQueue{

    private List<Integer> queue;
    private List<Integer> max;

    public MaxQueue(){
        queue = new LinkedList<>();
        max = new LinkedList<>();
    }

    public int max_value() {
        return max.size() > 0 ? max.get(0) : -1;
    }
    
    public void push_back(int value) {
        queue.add(value);
        while(max.size() > 0 && max.get(max.size()-1) < value){
            max.remove(max.size()-1);
        }
        max.add(value);
    }
    
    public int pop_front() {
        int tmp = -1;
        if(queue.size() > 0){
            tmp = queue.get(0);
            queue.remove(0);
            if(tmp == max.get(0)){
                max.remove(0);
            }
        }
        return tmp;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章