1. 程式人生 > >Leetcode|Sliding Window Maximum(multiset,優先佇列,雙端佇列和區間樹的應用)

Leetcode|Sliding Window Maximum(multiset,優先佇列,雙端佇列和區間樹的應用)

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

For example,
Given nums = [1,3,-1,-3,5,3,6,7]

, and k = 3.

Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

Therefore, return the max sliding window as [3,3,5,5,6,7].

1,暴力解法:最低效的時間複雜度O(k*(n-k+1));,

2,STL中的內建容器的排序功能。(O(n*logk))

為了熟悉STL,使用優先佇列和nultiset來解決,雖然時間複雜度不是最優的。

//藉助紅黑樹的排序方法,在leetcode擊敗15%的人,效率很低啦。
vector<int> maxSlidingWindow(vector<int>& nums, int k) {//利用multiset中紅黑樹的排序功能 時間複雜度O(nlogk)
        int n=nums.size();
        vector<int> max;
        multiset<int> smax;
        for(int i=0;i<n;i++){
            if(i>=k) smax.erase(smax.find(nums[i-k]));//每次刪除過期的一個元素,每次都得對k個數重新排序
            smax.insert(nums[i]);
            if(i>=k-1) max.push_back(*smax.rbegin());//找到排在最後的數,就是最大的
        }
        return max;
    }
//藉助STL的heap操作,擊敗了20%l的leetcoder。
vector<int> maxSlidingWindow(vector<int>& nums, int k) {//優先佇列是一種配接器,底層是vector
        int n=nums.size();
        vector<int> max;
        priority_queue<pair<int,int>> qmax;
        for(int i=0;i<n;i++){
            while(!qmax.empty()&&qmax.top().second<=i-k){
                qmax.pop();
            }
            qmax.push(make_pair(nums[i],i));
            if(i>=k-1) max.push_back(qmax.top().first);
        }
        return max;
    }

3,線段樹(時間複雜度O(n*log(k)) 擊敗了26%的leetcoder.

線段樹節點的三個數值元素為:start,end,max(記錄區間的最大值),是以一個vector作為底層容器實現。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> res;
        if(k<=0||n==0||k>n) return res;
        SegmentTreeNode *root=build_segmentTree(0,k-1,nums);
        res.push_back(root->max);
        for(int i=k;i<n;i++){
            modify(i%k,nums[i],root);  
            res.push_back(root->max);
        }
        return res;
    }
private:
    struct SegmentTreeNode{
        int start,end,max;
        SegmentTreeNode *left,*right;
        SegmentTreeNode(int x,int y,int m):start(x),end(y),max(m),left(NULL),right(NULL){ }
    };
    
    SegmentTreeNode* build_segmentTree(int start,int end,vector<int>& a){
        if(start>end||a.size()==0||end>=a.size()) return NULL;
        SegmentTreeNode* root=new SegmentTreeNode(start,end,a[start]);
        if(start==end) return root;
        int mid=(start+end)/2;
        root->left=build_segmentTree(start,mid,a);
        root->right=build_segmentTree(mid+1,end,a);
        root->max=max(root->left->max,root->right->max);
        return root;
    }
    
    void modify(int dot,int newval,SegmentTreeNode* root){
        if(dot<root->start||dot>root->end) return;
        if(root->start==root->end){
            root->max=newval;
            return;
        }
        int mid=(root->start+root->end)/2;
        if(dot<=mid) modify(dot,newval,root->left);
        else modify(dot,newval,root->right);
        root->max=max(root->left->max,root->right->max);
    }
    
};

4,,最優解O(n):雙端佇列 擊敗50%leetcoder

deque來儲存下標值,這樣可以獲得元素大小和位置兩組資訊。佇列元素個數永遠小於等於k,從大到小排序。擊敗50%的leetcoder

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> max;
        deque<int> qmax;
        for(int i=0;i<n;i++){
            while(!qmax.empty()&&nums[qmax.back()]<=nums[i]){//隊尾的值實時更新
                qmax.pop_back();
            }
            qmax.push_back(i);
            if(qmax.front()==i-k){//更新過期的隊首
                 qmax.pop_front();
            }
            if(i>=k-1){
                max.push_back(nums[qmax.front()]);
            }
        }
        return max;
    }