<LeetCode> 题362:最大滑动窗口

1. 题目描述:

给出一个可能包含重复的整数数组,和一个大小为 k 的滑动窗口, 从左到右在数组中滑动这个窗口,找到数组中每个窗口内的最大值。
注意:假设输入的k在数组大小范围之内。
例如:
给出数组 [2, 3, 4, 2, 6, 2, 5, 1],滑动窗口大小为 k = 3,则返回 [4,4,6,6,6, 5]。
[2, 3, 4], 2, 6, 2, 5, 1 –> max = 4
2, [3, 4, 2], 6, 2, 5, 1 –> max = 4
2, 3, [4, 2, 6], 2, 5, 1 –> max = 6
2, 3, 4, [2, 6, 2], 5, 1 –> max = 6
2, 3, 4, 2, [6, 2, 5], 1 –> max = 6
2, 3, 4, 2, 6, [2, 5, 1] –> max = 5

2. 思路1:暴力解法

以k为单位遍历数组,每移动一次,计算这一次k个数中的最大值,最后输出每一次移动得到的最大值数组。
时间复杂度:计算窗口最大值需要O(k),窗口移动n-k+1次,所以总的时间复杂度为O(nk)。

2.1 数组Array方式实现:

class Solution
{
public:
    int getMax(const int A[],int size)
    {
        int max = A[0];
        for(int i = 1;i < size;i++)
        {
            if(A[i] > max)
                max = A[i];
        }
        return max;
    }
    vector<int> maxSlidingWindow(const int A[],int size,int n)
    {
        vector<int> result;
        for(int i = 0;i <= n - size;i++)
        {
            int num = getMax(A + i,size);
            result.push_back(num);
        }
        return result;
    }
};

2.2 向量Vector方式实现:

class Solution
{
public:
    vector<int> maxSlidingWindow(const vector<int>& num, unsigned int size)
    {
        vector<int> res;
        if(num.size() == 0 || size == 0)
        {
            return res;
        }

        for(int start = 0; start <= (int)(num.size( ) - size); start++)
        {
            int end = start + size;
            int max = INT_MIN;
            for(int index = start; index < end; index++)
            {
                if(num[index] > max)
                {
                    max = num[index];
                }
            }

            cout <<"[" << start <<", " << end - 1 <<"], max = " <<max <<endl; //输出每个窗口的最大值
            res.push_back(max);
        }

        return res;
    }
};

3. 思路2:最大堆方法

构建一个窗口k大小的最大堆,每次从堆中取出窗口的最大值,随着窗口往右滑动,需要将堆中不属于窗口的堆顶元素删除。
时间复杂度:正常情况下,往堆中插入数据为O(lgk),如果数组有序,则为O(lgn),因为滑动过程中没有元素从堆中被删除,滑动n-k+1次,复杂度为O(nlgn)。

class Solution
{
public:
    vector<int> maxSlidingWindow(const vector<int> &num, unsigned int size)
    {
        vector<int> result;
        typedef pair<int, int> Pair;
        priority_queue<Pair> Q;

        if (num.size() < size || size < 1)
            return result;
        for (int i = 0; i < size - 1; i++) 
            Q.push(Pair(num[i],i));
        for (size_t i = size - 1; i < num.size(); i++)
        {
            Q.push(Pair(num[i],i));
            Pair p = Q.top();
            while(p.second < i- (size - 1))
            {
                Q.pop();
                p = Q.top();
            }
            result.push_back(p.first);
        }
        return result;
    }
};

4. 思路3:两个栈来实现

引用自:http://blog.csdn.net/gatieme/article/details/51915826

实际上一个滑动窗口可以看成是一个队列。当窗口滑动时,处于窗口的第一个数字被删除,同时在窗口的末尾添加一个新的数字。这符合队列的先进先出特性。如果能从队列中找出它的最大数,这个问题也就解决了。

其实题《带最小值的栈》中我们实现了一个可以用O(1)时间得到最小值的栈。同样,也可以用O(1)时间得到栈的最大值。同时在《剑指offer》面试题7中,提供了如何用两个栈实现一个队列。综合这两个问题的解决方法,我们发现如果把队列用两个栈来实现,由于可以用O(1)时间得到栈中的最大值,那么也就可以用O(1)时间得到队列的最大值,因此总的时间复杂度也就降到了O(n)。因此我们现在的问题归结为:实现一个尽可能快的找出队列最大值。

A栈,B栈,这两个栈都是前面提到的pop-push-min复杂度都为O(1)的空间换时间的实现。
取最值:返回A栈的最值和B栈的最值相比后的最值。复杂度O(1)。

入队操作:直接入到B栈中。复杂度O(1)。
出队操作:如果A栈不为空,直接A栈出栈,复杂度为O(1),如果A栈为空,那么将B栈内容逐个出栈并且逐个入栈到A中,然后A栈出栈,复杂度O(N),实际上是B栈的长度。

对于这种方法,如果对队列操作时,一连串的入栈,然后是一连串的出栈,那么就是首先不停向B入栈,然后第一个出栈,B栈元素全压入A栈,A出栈一个,这一步是N的复杂度,但是此后是不停的从A出栈,这都是O(1)的复杂度。而且借助了栈的代码,方便实现。对于这样的情景,就是只有第一个出栈的时候,要O(N),复杂度不是很均匀。对于每个元素来说,要么入B栈,入A栈,从A栈弹出,即总体是3N,平均下来基本上是O(3),要比最大堆的O(LogN)是快了不少呢。

#define MAX 100
class Stack
{
private:
    int datastack[MAX];
    int maxstack[MAX];
    int stackTop;
    int maxValue;
public:
    Stack() : stackTop(-1), maxValue(-1) {}
    int size() { return stackTop + 1; }
    int empty() { return stackTop < 0 ? 1 : 0; }

    void push(int val)
    {
        ++stackTop;
        if(stackTop == MAX)
        {
            cout << "The stack has been full!" << endl;
            return;
        }
        else
        {
            datastack[stackTop] = val;
            if(max() < val)
            {
                maxstack[stackTop] = maxValue;
                maxValue = stackTop;
            }
            else
                maxstack[stackTop] = -1;
        }
    }

    int pop()
    {
        int ret;
        if(stackTop == -1)
        {
            cout << "The stack is empty!" << endl;
            return -1;
        }
        else
        {
            ret = datastack[stackTop];
            if(stackTop == maxValue)
            {
                maxValue = maxstack[stackTop];
            }
            --stackTop;

            return ret;
        }
    }

    int max()
    {
        if(maxValue >= 0)
            return datastack[maxValue];
        else
            return -100;
    }

};
class Queue
{
private:
    Stack stackIn;
    Stack stackOut;
public:
    int size( )
    {
        return stackIn.size( ) + stackOut.size( );
    }
    int max( )
    {
        return std::max(stackIn.max( ), stackOut.max( ));
    }

    void enQueue(int val)
    {
        stackIn.push(val);
    }

    int deQueue()
    {
        if(stackOut.empty() && !stackIn.empty())
        {
            while(!stackIn.empty())
                stackOut.push(stackIn.pop());
        }
        return stackOut.pop();
    }
};

class Solution
{
public:
    vector<int> maxSlidingWindow(const vector<int> &num, unsigned int size)
    {
        unsigned int length = num.size( );
        vector<int> res;

        if(length == 0 || size == 0 || length < size)
        {
            return res;
        }

        Queue que;
        for(int i = 0; i < num.size( ); i++)
        {
            if(que.size( ) < size)
            {
                que.enQueue(num[i]);
            }
            else
            {
                res.push_back(que.max( ));

                que.enQueue(num[i]);
                que.deQueue( );
            }
        }
        if(que.size( ) == size)
        {
            res.push_back(que.max( ));
        }

        return res;
    }
};

5. 思路4:双向队列法

最大堆解法在堆中保存有冗余的元素,比如原来堆中元素为[10 5 3],新的元素为11,则此时堆中会保存有[11 5 3]。其实此时可以清空整个队列,然后再将11加入到队列即可,即只在队列中保持[11]。使用双向队列可以满足要求,滑动窗口的最大值总是保存在队列首部,队列里面的数据总是从大到小排列。当遇到比当前滑动窗口最大值更大的值时,则将队列清空,并将新的最大值插入到队列中。如果遇到的值比当前最大值小,则直接插入到队列尾部。每次移动的时候需要判断当前的最大值是否在有效范围,如果不在,则需要将其从队列中删除。由于每个元素最多进队和出队各一次,因此该算法时间复杂度为O(N)。
具体思路就是:用双向队列保存数字的下标,遍历整个数组,如果此时队列的首元素是i - k的话,表示此时窗口向右移了一步,则移除队首元素。然后比较队尾元素和将要进来的值,如果小的话就都移除,然后此时我们把队首元素加入结果中即可。

class Solution
{
public:
    vector<int> maxSlidingWindow(vector<int> &nums, int size)
    {
        vector<int> res;
        deque<int> q;
        for (int i = 0; i < nums.size(); ++i)
        {
            if (!q.empty() && q.front() == i - size) 
            {
                q.pop_front();
            }
            while (!q.empty() && nums[q.back()] < nums[i]) 
            {
                q.pop_back();
            }
            q.push_back(i);
            if (i >= size - 1) 
            {
                res.push_back(nums[q.front()]);
            }
        }
        return res;
    }

};

ps:完整测试程序代码见我的代码片 maxSlidingWindow.cpp

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值