每日一题——滑动窗口的最大值

简介: 每日一题——滑动窗口的最大值

滑动窗口的最大值

题目链接


暴力解法

最容易想到的当然还是通过两层循环来暴力求解:一层循环用来移动窗口,一层循环用来在窗口内找到最大值。这种做法的时间复杂度为O(kN),会超出时间限制,因此,我们要找到更加高效的方法。

单调队列

注:这种解法建立在单调队列的基础之上,而单调队列是双端队列的特殊形式,如果对单调队列和双端队列还不太了解,建议先看看👉详解单调队列

由于我们要求的是滑动窗口的最大值,那我们不妨先做一个假设:如果当前的滑动窗口中有两个下标ij,其中**ij的左侧(i < j,并且i对应的元素不大于j对应的元素(nums[i] <= nums[j])**。

那么我们就可以得到这样一个结论:只要下标i所代表的元素nums[j]还在窗口中,那么下标j所对应的元素nums[j]也一定在窗口中,且**nums[i]一定不会是最大值**,也就不会对答案造成影响,我们也就可以将其直接删除

也可以用一句话来概括:

如果一个数据val要进入窗口,那他窗口内所有比它小或等于它的的数都不会对答案造成影响

我们可以用一个队列来存储这些还没有被移除的元素的下标,同时确保从队头到队尾,这些下标是从小到大排列的(保证数据的愿所有顺序),而下标所代表的值是递减排列的,这样队头元素就是滑动窗口的最大值了。

每当窗口向右滑动一个元素,就会有一个新的元素入队。在这个元素入队之前,由于我们要确保队列的单调递减性,因此当队列不为空并且队尾元素小于或等于新的元素时就要通过循环删除这些不会对结果造成影响的元素,最后再把这个元素的下标插入队列。

需要注意:当窗口向右滑动时,最大值下标会离开窗口,永远不会在进入窗口,因此在窗口滑动的过程中,我们要不断比较队首元素的下标(最大值下标)和窗口最左侧的标(下次移动时离开窗口的元素下标),如果离开了窗口,就要将队首元素出队。

例如对于上图的示例:

实现代码

typedef struct DequeNode
{
    struct DequeNode* next;
    struct DequeNode* prev;
    int data;
}DQNode;
typedef struct Deque
{
    DQNode* front;
    DQNode* tail;
}Deque;
void DequeInit(Deque* pq)   //双端队列初始化
{
    assert(pq);
    pq->front = pq->tail = NULL;
}
bool DequeEmpty(Deque* pq)  //双端队列判空
{
    assert(pq);
    return pq->front == NULL;
}
void DequePushTail(Deque* pq, int val)  //队尾入队
{
    DQNode* newNode = (DQNode*)malloc(sizeof(DQNode));
    newNode->data = val;
    newNode->next = NULL;
    newNode->prev = NULL;
    if (DequeEmpty(pq))
    {
        pq->front = pq->tail = newNode;
    }
    else
    {
        pq->tail->next = newNode;
        newNode->prev = pq->tail;
        pq->tail = newNode;
    }
}
void DequePushFront(Deque* pq, int val) //队头入队
{
    DQNode* newNode = (DQNode*)malloc(sizeof(DQNode));
    newNode->data = val;
    newNode->next = NULL;
    newNode->prev = NULL;
    if (DequeEmpty(pq))
    {
        pq->front = pq->tail = newNode;
    }
    else
    {
        newNode->next = pq->front;
        pq->front->prev = newNode;
        pq->front = newNode;
    }
}
int DequePopFront(Deque* pq)    //队头出队
{
    assert(pq);
    assert(!DequeEmpty(pq));
    DQNode* temp = pq->front;
    int ret = temp->data;
    pq->front = pq->front->next;
    if (pq->front == NULL)
        pq->tail = NULL;
    else
        pq->front->prev = NULL;
    free(temp);
    return ret;
}
int DequePopTail(Deque* pq) //队尾出队
{
    assert(pq);
    assert(!DequeEmpty(pq));
    DQNode* temp = pq->tail;
    int ret = temp->data;
    pq->tail = pq->tail->prev;
    if (pq->tail == NULL)
        pq->front = NULL;
    else
        pq->tail->next = NULL;
    free(temp);
    return ret;
}
int DequeTail(Deque* pq)    //取队尾元素
{
    assert(pq);
    assert(!DequeEmpty(pq));
    return pq->tail->data;
}
int DequeFront(Deque* pq)   //取队头元素
{
    assert(pq);
    assert(!DequeEmpty(pq));
    return pq->front->data;
}
void DequeDestroy(Deque* pq)    //销毁双端队列
{
    while (!DequeEmpty(pq))
    {
        DQNode* temp = pq->front;
        pq->front = pq->front->next;
        free(temp);
    }
    free(pq);
}
//-------------------------------------双端队列基本功能实现-----------------------------------------------
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize){
    *returnSize = numsSize - k + 1;
    int* ret = (int*)malloc(sizeof(int) * (*returnSize));
    Deque* DQ_Max = (Deque*)malloc(sizeof(Deque));  //用来记录最大值下标
    DequeInit(DQ_Max);  //初始化
    //先将数组前k个入队列,构建初始窗口
    for (int i=0; i<k; i++)
    {
        //队列不为空且队尾元素小于或等于新元素,就将队尾元素删除
        //确保递减
        while (!DequeEmpty(DQ_Max) && nums[DequeTail(DQ_Max)] <= nums[i])
        {
            DequePopTail(DQ_Max);
        }
        DequePushTail (DQ_Max, i);  //将下标入队
    }
    ret[0] = nums[DequeFront(DQ_Max)];  //初始窗口的最大值就是队头下下标所代表的元素
    //再将后面剩余的元素下标入队
    for (int i=k; i<numsSize; i++)
    {
        //删除不在窗口的下标
        while (!DequeEmpty(DQ_Max) && DequeFront(DQ_Max) <= i - k)
            DequePopFront(DQ_Max);
        //队列不为空且队尾元素小于新元素,就将队尾元素删除
        //确保非递增
        while (!DequeEmpty(DQ_Max) && nums[DequeTail(DQ_Max)] <= nums[i])
        {
            DequePopTail(DQ_Max);
        }
        DequePushTail (DQ_Max, i);
        //窗口最大值就是队头下下标所代表的元素
        ret[i - k + 1] = nums[DequeFront(DQ_Max)];
    }
    //销毁队列,释放内存
    DequeDestroy(DQ_Max);
    return ret;
}
相关文章
|
存储 开发工具 git
【软考学习16】用位示图法,轻松解决空闲存储空间的管理难题
【软考学习16】用位示图法,轻松解决空闲存储空间的管理难题
924 0
|
SQL Java 数据库连接
Pagehelper超级好用的分页插件
Pagehelper超级好用的分页插件
2267 0
|
算法
基于matlab的OFDM通信链路仿真,输出OFDM频谱,星座图,收发时域波形
基于matlab的OFDM通信链路仿真,输出OFDM频谱,星座图,收发时域波形
742 0
基于matlab的OFDM通信链路仿真,输出OFDM频谱,星座图,收发时域波形
|
数据可视化 关系型数据库 MySQL
Install MySQL Workbench 8.0 CE | MySQL
MySQL 数据库及服务的安装 MySQL 数据库,关系型数据库管理系统,一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据
710 0
Install MySQL Workbench 8.0 CE | MySQL
|
前端开发
前端学习笔记202305学习笔记第二十九天-React keep alive原理之4
前端学习笔记202305学习笔记第二十九天-React keep alive原理之4
81 0
|
存储 消息中间件 缓存
面试 Redis 没底?这 40 道面试题让你不再慌(附答案)
面试 Redis 没底?这 40 道面试题让你不再慌(附答案)
17407 1
|
Web App开发 Java
myeclipse 2017 CI 中如何修改Servlet模板
myeclipse 2017 CI 中如何修改Servlet模板   在实际开发中,这些生成的代码和注释一般我们都用不到的,每次都要手工删除这些注释和代码,很麻烦,因此可以根据开发的实际情况修改Servlet的模板代码,改成符合实际开发需求的模板代码。
1628 0
|
3天前
|
云安全 人工智能 自然语言处理