题目

数组num的长度为N,求其所有长度为W的连续区间的最大值。

一、平衡树

直接用map来对每个出现的值计数,map可以直接加入,删除,求最大。
时间复杂度O(NlogW)
空间复杂度O(N)

二、Sparse Table

利用Sparse_Table算法思想,将区间[A, A + W) 分解成[A, A+R) 和[A+W-R, A+W) 其中R是满足 2*R >= W的最小的2的次方。分解成的两个小区间有重叠,但由于是求最值,重叠部分不会影响答案。预处理部分中需要得到从每个下标A开始长度为1,2,4,8,...,R的区间的最值。相比Sparse_Table算法,这里的空间可以优化成O(N),因为只需要保留长度为R的数组。
时间复杂度O(NlogW)
空间复杂度O(N)

三、单调队列

利用这条性质:当A < B且num[A] < num[B]时,num[A]对右端点在B或之后的区间来说可以忽略,因为num[B]是个更优解且num[A]先过期。
于是维护一个单调队列,队列中的元素由高到低排列(队列中存下标,方便判断过期)。
从小到大扫描num数组,当考虑到下标B时,根据上面的性质,可以安全地从队尾删除所有比num[B]小的值。
然后将B加入队尾。
然后从队首删除所有过期的值。
做完以上3点之后,队首的值即为以B为右端点的区间的最大值。
时间和空间复杂度都是O(N),因为每个元素只进出一次队列。
时间复杂度O(N)
空间复杂度O(N)

四、代码

class Solution {
public:
    //平衡树
    vector<int> maxInWindows1(const vector<int>& num,int W)
    {
        int N = num.size();
        vector<int> ret;
        map<int, int> Count;

        for(int i = 0 ; i < N ; ++i){
            //加入当前值
            ++Count[num[i]];
            //删除过期元素
            if(i - W >= 0 && 0 == --Count[num[i - W]])
                Count.erase(num[i - W]);
            //计算答案
            if(i >= W - 1)
                ret.push_back(Count.rbegin()->first);
        }
        return ret;
    }
    //Sparse Table
    vector<int> maxInWindows2(const vector<int>& num,int W)
    {
        int N = num.size();

        vector<int> Max(num.begin(), num.end());
        int MaxRange = 1;
        //Max[i]为区间[i, i + MaxRange)中的最大值
        //每次循环将MaxRange翻倍并保持此性质

        while(MaxRange * 2 < W){
            for(int i = 0 ; i + 2 * MaxRange <= N ; ++i)
                Max[i] = max(Max[i], Max[i+MaxRange]);
            MaxRange *= 2;
        }
        //此时 MaxRange * 2 >= W,即MaxRange至少覆盖半个窗口

        vector<int> ret;
        for(int i = 0 ; i + W <= N; ++i){
            // [i, i + W)被分成[i, i + MaxRange)
            // 和 [i + W - MaxRange, i + W)这两个区间。
            ret.push_back(max(Max[i], Max[i+W-MaxRange]));
        }
        return ret;
    }
    //单调队列
    vector<int> maxInWindows3(const vector<int>& num, int W)
    {
        int N = num.size();

        vector<int> ret;
        list<int> L;
        for(int i = 0 ; i < N ; ++i){
            //从队尾删除比num[i]小的数
            while(!L.empty() && num[*L.rbegin()] < num[i])
                L.pop_back();

            //将i加入队尾
            L.push_back(i);

            //从队首删除过期的数
            while(!L.empty() && (*L.begin() <= i - W))
                L.pop_front();

            //将以i结尾的区间最值加入答案
            if(i >= W - 1)
                ret.push_back(num[*L.begin()]);
        }
        return ret;
    }
};