滑动窗口中的最大值

题目描述

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值
例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3

那么一共存在6个滑动窗口, 他们的最大值分别为{4,4,6,6,6,5};

针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个

{[2,3,4],2,6,2,5,1},最大值4

{2,[3,4,2],6,2,5,1},最大值4

{2,3,[4,2,6],2,5,1},最大值6

{2,3,4,[2,6,2],5,1},最大值6

{2,3,4,2,[6,2,5],1},最大值6

{2,3,4,2,6,[2,5,1]},最大值5
--------------------- 
 

3 暴力解法


如果采用蛮力法,这个问题似乎不难解决:可以扫描每一个滑动窗口的所有数字并找出其中的最大值。如果滑动窗口的大小为k,需要O(k)时间才能找出滑动窗口里的最大值。对于长度为n的输入数组,这个算法总的时间复杂度是O(nk)

#include <iostream>
#include <vector>
#include <deque>
#include <iterator>

#include <climits>

using namespace std;


//  调试开关
#define __tmain main

#ifdef __tmain

#define debug cout

#else

#define debug 0 && cout

#endif // __tmain

/*  单调队列,O(n)
 *  引用马客(Mark)的解题思路,马客没加注释,我用自己的理解加下注释,希望对你们有用,
 *  如有错误,见谅,我会及时修改
 *  deque s中存储的是num的下标  */

class Solution
{
public:
    vector<int> maxInWindows(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];
                }
            }

            debug <<"[" <<start <<", " <<end <<"], max = " <<max <<endl;
            res.push_back(max);
        }

        return res;
    }

};



int __tmain( )
{
    Solution solu;

    int array[] = { 2, 3, 4, 2, 6, 2, 5, 1 };
    vector<int> vec(array, array + 8);

    vector<int> res = solu.maxInWindows(vec, 3);
    copy(res.begin( ), res.end( ), ostream_iterator<int>(cout," "));

    return 0;
}

4 队列中取最大值操作问题

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

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

起初没仔细看,还以为与此前的自定义栈-pop-push-min-时间复杂度都为O(1) 是一样的,后来才发现不是一回事,有差别的。对于栈来说,我们可以的入栈和出栈不会影响辅助数组内的情况,假设当前N个元素,(为了说明简单,下标从1开始),辅助空间的F[1]记录的是A[1,1]内的最值位置,F[2]记录的是A[1,2]内的最值位置,···,F[N]记录的是A[1,N]内的最值位置。在插入F[k+1]=A[F[k]]与A[k+1]两者最值的位置,插入复杂度为O(1),在删除第k+1个节点时,删除A[k+1]和F[k+1],这并不影响F[1]-F[k],因此删除的复杂度为O(1),取最值,假设当前N个元素,即返回A[F[N]]。 
对于队列来说,如果套用栈的辅助数组方法,假设当前有N个元素,下标从1开始,那么F[1]记录的是A[1,N]中的最值,F[2]记录的是A[2,N]中的最值,···,F[N]记录的是A[N,N]中的最值。当A[k+1]入队时,需要更新F[1]到F[k],F[K+1]=K+1,因此插入的复杂度为O(N),当F[k]出队时,删除F[k]即可(每次出队的都是第一个元素,实际上此时F[1]-F[k-1]已经出队完毕了),因此删除的复杂度为O(1)。取最值的复杂度是O(1)。

队列与栈的区别很清楚了。我们在编程之美上找到了两个答案 
* 一个是构建最大堆 
* 另一个是用两个栈来实现。
 

4.1 最大堆的方法

队列本身要么顺序结构要么链接结构,还那么存。另外对于队列每个元素构建一个节点(包含在队列中的位置),这些节点构成一个最大堆,因此插入和删除操作都要维护这个最大堆,时间复杂度都是O(LogN),取最大值的复杂度为 O(1)。
1
暴力的思路简单,但是时间复杂度过高,因此需要改进。可以使用一个最大堆来保存size个数字,每次插入数字时只需要O(lgsize)的时间,从堆中取最大值只需要O(1)的时间。

随着窗口由左向右滑动,因此堆中有些数字会失效(因为它们不再包含在窗口中)。

class Solution
{
    typedef pair<int,int> Pair;
public :
    vector<int> maxInWindows(const vector<int> &num, unsigned int size)
    {
        vector<int> result;
        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 (int 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);
        }
//        result.push_back(Q.top().first);
        return result;
    }
};


 

猜你喜欢

转载自blog.csdn.net/u010325193/article/details/86550950
今日推荐