为了更好的阅读体验,欢迎阅读原文:

[思维提升|干货All in]6种算法解决LeetCode困难题:滑动窗口最大值 (eriktse.com)

最近在leetcode遇到一道非常经典的题目:239. 滑动窗口最大值 - 力扣(LeetCode)

以前只会看题解用单调队列做,最近研究一下发现是一道很好的题,可以帮助我们提升“维护区间最值”的算法思维。

先介绍一下我解决这题所用的算法及其复杂度:

  • 单调队列 O(n)
  • st表 O(nlogn)
  • 树状数组 O(n(logn)^2)
  • 多重集合法 O(nlogn)
  • 莫队O (n sqrt{n})
  • 优先队列 O(nlogn)

首先确定一点,单调队列是解决这道题最好的办法,但是其他的方法的适用范围更广。

1、单调队列

首先可以参考几篇优秀的文章:

算法学习笔记(66): 单调队列 - 知乎 (zhihu.com)

单调队列 - OI Wiki (oi-wiki.org)

我这里提几点值得注意的地方:

1.单调队列中存放的是下标,而不是元素值

2.单调队列是一个双端队列,尾插前先查队头后查队尾

3.单调队列维护的是元素值的单调性

有了这几点注意,代码就很好写了:

class Solution {
public:
static const int maxn = 1e5 + 9;
int a[maxn];
deque<int> dq;
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res;
int n = nums.size();
for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1];
for(int i = 1;i <= n; ++ i)
{
int x = a[i];
while(!dq.empty() and dq.front() < i - k + 1)dq.pop_front();
while(!dq.empty() and x >= a[dq.back()])dq.pop_back();
dq.push_back(i);
if(i >= k)res.push_back(a[dq.front()]);
}
return res;
}
};

我做题习惯把输入数组存一个array,大家请勿介意。

2.st表

st表是一种基于DP(动态规划)思想的算法,也算是一种数据结构吧。

st表可以静态维护区间的最值,需要用的时间来预处理,后可以O(1)查询。

我们定义dp[i][j]表示

表示区间的最值,在这道题里我们认为是最大值(维护最小值同理)。看一下能不能开下,大概是maxn * 20的大小,可以开下。

通过定义不难发现,dp[i][0] = a[i],因为此时区间长度为1,那么最值就是元素a[i]本身。当j增大时,我们有转移方程(具体的原因可简单自行推导,以后的文章中也会有讲解):

dp[i][j] = max(dp[i][j - 1], dp[i + 2^(j-1)][j - 1])

查询十分方便,方法是从两边往中间尽可能多地覆盖。假设要查询的区间是[l,r]

,那么我们可以得到区间长度r - l + 1,现在求一个比长度小且最大的2的幂,然后把左右两块取大即可。

直接看代码:

class Solution {
public:
static const int maxn = 1e5 + 9;
int a[maxn], st[maxn][30];//st[i][j]表示[i, i + 2^j - 1]的最大值
int queMax(int l, int r)
{
int k = log(r - l + 1) / log(2);
return max(st[l][k], st[r - (1<<k) + 1][k]);
}
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
memset(st, 0xcf, sizeof st);
vector<int> res;
int n = nums.size();
for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1];
for(int i = 1;i <= n; ++ i)st[i][0] = a[i];
for(int k = 1;k <= 20; ++ k)
{
for(int i = 1;i <= n and i + (1 << (k - 1)) <= n; ++ i)
{
st[i][k] = max(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]);
}
} for(int i = 1;i + k - 1 <= n; ++ i)res.push_back(queMax(i, i + k - 1)); return res;
}
};

3、树状数组

看见树状数组可能有小朋友会感到疑惑了哦,树状数组不是维护区间和的吗?怎么还来凑“区间最值”的热闹了。

其实树状数组不仅可以维护区间和,还可以“动态维护区间最值”,但是维护的方法和区间和略有不同。这一次主要看一下代码吧,具体的原理之后再讲。

树状数组节点t[k]维护的是区间[k - lowbit(k) + 1, k]。

树状数组主要突出的优点就是可以动态维护,但是注意在维护区间最值的时候仅可单点修改,不支持区间修改。

class Solution {
public:
static const int maxn = 1e5 + 9;
//树状数组
int a[maxn], t[maxn], n;//t[i] 表示 a[i - lowbit(i) + 1] ~ a[i]的最大值
int lowbit(int x){return x & -x;} void update(int k, int x)// modify a[k] to x
{
a[k] = x;
while(k <= n)
{
t[k] = x;
for(int i = 1;i < lowbit(k); i <<= 1)t[k] = max(t[k], t[k - i]);
k += lowbit(k);
}
} int queMax(int l, int r)
{
int res = a[r];//单点查询
while(l <= r)
{
for(;r - lowbit(r) >= l; r -= lowbit(r))res = max(res, t[r]);
res = max(res, a[r --]);
}
return res;
} vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
n = nums.size();
for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1];
vector<int> res;
for(int i = 1;i <= n; ++ i)
{
update(i, a[i]);
if(i >= k)res.push_back(queMax(i - k + 1, i));
} return res;
}
};

4、多重集合法

这种方法就十分简单粗暴了,就是维护一个不断移动的multiset,简直是暴力之王。

class Solution {
public: vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
multiset<int> st;
vector<int> res;
for(int i = 0;i < k; ++ i)st.insert(nums[i]);
res.push_back(*st.rbegin());
for(int i = k;i < nums.size(); ++ i)
{
st.erase(st.find(nums[i - k]));
st.insert(nums[i]);
res.push_back(*st.rbegin());
} return res;
}
};

5、莫队

莫队需要基于multiset,在这道题里的优势并不明显,因为这题的询问都是有顺序的,但是可以写个莫队练个手。

莫队在处理随机区间查询问题的时候有独特的优势,因为足够暴力,所以维护的东西可以很多很杂,比如区间和,区间最值,区间元素种类数等。

以后我会详细讲莫队的,欢迎大家访问我的个人博客:https://www.eriktse.com

在右上角留下邮箱订阅我的博客,每周更新优质的算法/技术/互联网文章!

class Solution {
public:
static const int maxn = 1e5 + 9;
int a[maxn], pos[maxn], ans[maxn], n;
struct Q
{
int l, r, id;//询问离线
}q[maxn];//outline algorihm multiset<int> st; void Add(int k)//把a[k]加入到区间内
{
st.insert(a[k]);
} void Del(int k)
{
st.erase(st.find(a[k]));
} vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
n = nums.size();
for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1];
int siz = sqrt(n);
for(int i = 1;i <= n; ++ i)pos[i] = i / siz;
int m = n - k + 1;
for(int i = 1;i <= m; ++ i)q[i].l = i, q[i].r = i + k - 1, q[i].id = i; sort(q + 1, q + 1 + m, [this](const Q &u, const Q &v)
{
return pos[u.l] == pos[v.l] ? u.r < v.r : pos[u.l] < pos[v.l];
});
int L = 1, R = 0;//当前区间
for(int i = 1;i <= m; ++ i)
{
//[L, R] -> [l, r]
int l = q[i].l, r = q[i].r, id = q[i].id;
while(L < l)Del(L ++);
while(L > l)Add(-- L);
while(R > r)Del(R --);
while(R < r)Add(++ R);
ans[id] = *st.rbegin();
}
vector<int> res;
for(int i = 1;i <= m; ++ i)res.push_back(ans[i]);
return res;
}
};

6、优先队列

优先队列可以理解为一个“堆”结构。

我们知道优先队列可以维护最值,但是他只有一个堆顶怎么办呢?

我们只能保证堆顶是最大值但是却无法保证堆顶是在窗口内的呀!

为了解决这个问题,我们在每一次查询堆顶之前,都要对堆顶进行检查,直到堆顶在窗口内才能输出。

注意以下几点:

1.堆里存放的是下标,但是比较函数用值比较。

2.每次取出元素之前堆顶检查,只要堆顶的位置不在窗口内就一直弹出。

上代码!

const int maxn = 1e5 + 9;
int a[maxn];
class Solution {
public:
struct cmp{
bool operator ()(const int &u, const int &v)const
{
return a[u] < a[v];
}
};
priority_queue<int, vector<int>, cmp> pq;
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
int n = nums.size();
for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1]; vector<int> res;
for(int i = 1;i <= n; ++ i)
{
pq.push(i);
while(!pq.empty() and pq.top() < i - k + 1)pq.pop();
if(i >= k)res.push_back(a[pq.top()]);
} return res;
}
};

[思维提升|干货All in]6种算法解决LeetCode困难题:滑动窗口最大值的更多相关文章

  1. 利用深搜和宽搜两种算法解决TreeView控件加载文件的问题。

    利用TreeView控件加载文件,必须遍历处所有的文件和文件夹. 深搜算法用到了递归. using System; using System.Collections.Generic; using Sy ...

  2. [干货]Kaggle热门 | 用一个框架解决所有机器学习难题

    新智元推荐 来源:LinkedIn 作者:Abhishek Thakur 译者:弗格森 [新智元导读]本文是数据科学家Abhishek Thakur发表的Kaggle热门文章.作者总结了自己参加100 ...

  3. [DeeplearningAI笔记]卷积神经网络3.1-3.5目标定位/特征点检测/目标检测/滑动窗口的卷积神经网络实现/YOLO算法

    4.3目标检测 觉得有用的话,欢迎一起讨论相互学习~Follow Me 3.1目标定位 对象定位localization和目标检测detection 判断图像中的对象是不是汽车--Image clas ...

  4. 算法入门:最大子序列和的四种算法(Java)

    最近再学习算法和数据结构,推荐一本书:Data structures and Algorithm analysis in Java 3rd 以下的四种算法出自本书 四种最大子序列和的算法: 问题描述 ...

  5. Java利用DES/3DES/AES这三种算法分别实现对称加密

    转载地址:http://blog.csdn.net/smartbetter/article/details/54017759 有两句话是这么说的: 1)算法和数据结构就是编程的一个重要部分,你若失掉了 ...

  6. nignx 负载均衡的几种算法介绍

    负载均衡,集群必须要掌握,下面介绍的负载均衡的几种算法.   1 .轮询,即所有的请求被一次分发的服务器上,每台服务器处理请求都相同,适合于计算机硬件相同.   2.加权轮询,高的服务器分发更多的请求 ...

  7. [BS-28] iOS中分页的几种算法

    iOS中分页的几种算法 总记录数:totalRecord 每页最大记录数:maxResult 算法一: totalPage = totalRecord % maxResult == 0 ? total ...

  8. TCP控制拥塞的四种算法:慢开始,拥塞避免,快重传,快恢复

    我们在开始假定: 1:数据是单方向传递,另一个窗口只发送确认. 2:接收方的缓存足够大,因此发送方的大小的大小由网络的拥塞程度来决定. 一:慢开始算法和拥塞避免算法 发送方会维持一个拥塞窗口,刚开始的 ...

  9. 提升PHP性能的21种方法

    提升PHP性能的21种方法. 1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成st ...

  10. 从零开始学C++之STL(四):算法简介、7种算法分类

    一.算法 算法是以函数模板的形式实现的.常用的算法涉及到比较.交换.查找.搜索.复制.修改.移除.反转.排序.合并等等. 算法并非容器类型的成员函数,而是一些全局函数,要与迭代器一起搭配使用. 算法的 ...

随机推荐

  1. python基础篇 13-模块的导入 安装第三方模块

    一.模块 一个python文件就是一个模块 标准模块(内置模块) 第三方模块 需要自己安装的 自己写的 需要导入的 import 一个模块的实质: 实际上就是把一个py文件从头到尾执行了一遍,main ...

  2. react+antd 导出excel文件(简单数据&多级表头)

    需求: 在基于react+antd进行开发的页面中,实现导出excel报表的功能 实际场景: 1.简单数据:单层表头+数据 2.复杂数据:多层表头+数据 实现方式: 1.简单数据 简单数据的导出使用了 ...

  3. QPushButton与Enter相链接

    ui->pushButton_login->setFocus(); // 设置默认焦点 ui->pushButton_login->setShortcut(QKeySequen ...

  4. curl post请求body体内传参数

    1. 传参格式 json function post_http($array='',$url) { $ch = curl_init(); $header = array('Content-Type: ...

  5. Markdown基础学习

    Markdown学习 一级标题 #加空格 标题二 二级标题 ##加空格 如此类推 标题三 或者Ctrl+123456 字体 加粗 两个** hello world 倾斜一个* hello world ...

  6. SSD目标检测网络解读(含网络结构和内容解读)

    SSD实现思路 SSD具有如下主要特点: 从YOLO中继承了将detection转化为regression的思路,一次完成目标定位与分类 基于Faster RCNN中的Anchor,提出了相似的Pri ...

  7. 记 第一次linux下简易部署 django uwsgi nginx

    1.首先确定django项目是跑起来的 2.装nginx  uwsgi ,网上教程一大堆 3.uwsgi的配置了 我是通过ini启动的 随意找个顺手的文件夹创建uwsgi.ini文件 我是在/home ...

  8. Ubuntu常用命令(二)

    clash 启动 #.clash -d . sudo /home/lizhenyun/clash/clash -d /home/lizhenyun/clash/ deb包安装 sudo dpkg -i ...

  9. hexo相对路径图片显示

    说明 hexo的图片默认不支持相对路径.需要配置 post_asset_folder 选项,设置从false改成true之后支持.但是要求图片目录必须和文件名相同. 由于我在typore下的markd ...

  10. hi,docker,docker的介绍

    一.docker的介绍 1.什么是docker: docker是一种虚拟化技术,小型的系统环境(linux)2.虚拟化技术: 在计算机中用例管理虚拟资源的一种手段 内存管理.软件虚拟化.硬件虚拟化(磁 ...