当前位置: 首页 > news >正文

深入浅出单调栈与单调队列

目录

  • 一、单调栈
    • 情形一:寻找一个数左边第一个小于它的数
    • 情形二:寻找一个数左边第一个小于它的数的下标
    • 情形三:寻找一个数右边第一个大于它的数
    • 情形四:寻找一个数右边第一个大于它的数的下标
  • 二、单调栈的应用
    • 2.1 单调栈模板题I
    • 2.2 单调栈模板题II
    • 2.3 Bad Hair Day
  • 三、单调队列
  • 四、单调队列的应用
    • 4.1 滑动窗口

一、单调栈

所谓单调栈,就是指满足单调性的栈结构:

  • 单调递增栈: 栈中元素从栈底到栈顶是递增的;
  • 单调递减栈: 栈中元素从栈底到栈顶是递减的。

例如对于单调递增栈,向其中插入元素的时候,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素:

stack<int> stk;void insert(int x) {while (!stk.empty() && stk.top() > x)  // 当stk.top() <= x时满足单调性stk.pop();stk.push(x);
}

单调栈可以用来在一个数组中寻找某一个元素左边(或右边)第一个大于(或小于或大于等于或小于等于)它的元素(或元素的下标)。

这句话看起来有些绕,接下来我们只考虑以下四种「基本情形」。

情形一:寻找一个数左边第一个小于它的数

给定一个长度为 n(≤105)n\,(\leq 10^5)n(105) 的数组 aaa,输出每个数左边第一个比它小的数,如果不存在则输出 −1-11

传统的暴力做法是双重循环:

#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int a[N];int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = 0; i < n; i++) {bool flag = false;for (int j = i - 1; ~j; j--) {if (a[j] < a[i]) {flag = true;cout << a[j] << ' ';break;}}if (!flag) cout << -1 << ' ';}return 0;
}

然而这种做法的复杂度是 O(n2)O(n^2)O(n2),利用单调栈,我们可以将复杂度降低至 O(n)O(n)O(n)

在指针 iii 从左往右遍历的过程中,我们可以用一个栈来保存 iii 左边的所有元素(不包括 iii 指向的元素),下标越大的元素越接近栈顶,下标越小的元素越接近栈底。

每次我们访问栈顶,只要栈顶元素大于等于 a[i]a[i]a[i],我们就将栈顶元素弹出,直至栈顶元素小于 a[i]a[i]a[i],此时输出栈顶元素并将 a[i]a[i]a[i] 压入栈中。 由于栈中保存了 iii 左边的所有元素,所以只要有答案,则答案一定在栈中。

📃 对证明不感兴趣的读者可以跳过这部分
讲到这里,可能会有读者好奇,栈不是一直在弹出元素吗,万一先前就把答案弹出去了怎么办?这里我们可以从数学的角度进行证明。假设对于 a[i]a[i]a[i],答案一定存在,即

∃0≤p<i,s.t.{a[p]<a[i],a[t]>a[p],t=p+1,⋯,i−1\exists\, 0\leq p<i,\quad \text{s.t.}\; \begin{cases} a[p]<a[i], \\ a[t]>a[p], \quad t= p+1,\cdots,i-1 \end{cases} 0p<i,s.t.{a[p]<a[i],a[t]>a[p],t=p+1,,i1

对于第二个约束,假设有某个 a[t]≤a[p]a[t]\leq a[p]a[t]a[p],那么可知 a[t]<a[i]a[t]<a[i]a[t]<a[i] 并且 a[t]a[t]a[t]a[p]a[p]a[p] 的右边,从而 a[t]a[t]a[t] 才应该是答案,矛盾!

下面证明,当指针指向 a[i]a[i]a[i] 时,a[p]a[p]a[p] 一定存在于栈中。
当指针指向 a[p]a[p]a[p] 时,这一轮循环结束后,a[p]a[p]a[p] 会被压入栈中,所以我们从 p+1p+1p+1 开始考虑。事实上,∀t∈[p+1,i−1]\forall t\in[p+1,i-1]t[p+1,i1],当指针指向 a[t]a[t]a[t] 时,无论栈怎么弹出元素,都不会弹出 a[p]a[p]a[p],这是因为栈弹出元素的前提是栈顶元素 ≥a[t]\geq a[t]a[t],而 a[p]<a[t]a[p]<a[t]a[p]<a[t] 所以不会被弹出,自然地,当指针指向 a[i]a[i]a[i] 时,a[p]a[p]a[p] 仍在栈中。

由于每个元素一定会被压入一次且至多弹出一次,因此操作次数至多是 2n2n2n,故总时间复杂度为 O(n)O(n)O(n)

#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int a[N], ans[N];stack<int> stk;int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = 0; i < n; i++) {while (!stk.empty() && stk.top() >= a[i]) stk.pop();if (!stk.empty()) ans[i] = stk.top();else ans[i] = -1;stk.push(a[i]);}for (int i = 0; i < n; i++) cout << ans[i] << ' ';return 0;
}

📝 代码完全可以简化,之所以这么写是为了方便统一格式。

情形二:寻找一个数左边第一个小于它的数的下标

和情形一类似,只不过这里我们寻找的是下标,如果不存在则输出 −1-11

只需对栈做一点小小的修改就能应对情形二。注意到之前我们寻找的是元素所以让栈去保存元素,现在我们寻找下标,所以让栈去保存元素的下标就可以了。

#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int a[N], ans[N];stack<int> stk;int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = 0; i < n; i++) {while (!stk.empty() && a[stk.top()] >= a[i]) stk.pop();  // 仅有两处修改if (!stk.empty()) ans[i] = stk.top();else ans[i] = -1;stk.push(i);  // 仅有两处修改}for (int i = 0; i < n; i++) cout << ans[i] << ' ';return 0;
}

情形三:寻找一个数右边第一个大于它的数

之前我们是在一个数的左边去寻找,所以让栈去保存这个数左边的所有数,类似地,现在需要让栈去保存这个数右边的所有数。

考虑将数组翻转(实际上不可能翻转,而是倒序遍历),因此情形三变成了「寻找一个数左边第一个大于它的数」,于是归结为情形一。

#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int a[N], ans[N];stack<int> stk;int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = n - 1; ~i; i--) {while (!stk.empty() && stk.top() <= a[i]) stk.pop();if (!stk.empty()) ans[i] = stk.top();else ans[i] = -1;stk.push(a[i]);}for (int i = 0; i < n; i++) cout << ans[i] << ' ';return 0;
}

情形四:寻找一个数右边第一个大于它的数的下标

结合情形二和情形三即可得出。

#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int a[N], ans[N];stack<int> stk;int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = n - 1; ~i; i--) {while (!stk.empty() && a[stk.top()] <= a[i]) stk.pop();if (!stk.empty()) ans[i] = stk.top();else ans[i] = -1;stk.push(i);}for (int i = 0; i < n; i++) cout << ans[i] << ' ';return 0;
}

不难发现,这四种情形只在第 16,17,2016,17,2016,17,20 行不同,其余部分的代码均相同,据此可以总结出以下三点区别:

  • 遍历顺序(以怎样的顺序遍历数组 aaa);
  • 比较方式(如何比较当前元素和栈顶元素);
  • 栈中存储的是什么(是元素本身还是元素的下标还是其他)。

二、单调栈的应用

2.1 单调栈模板题I

原题链接:AcWing 830. 单调栈

此题对应情形一,不再赘述,直接给出AC代码。

#include <bits/stdc++.h>using namespace std;stack<int> stk;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;while (n--) {int x;cin >> x;while (!stk.empty() && stk.top() >= x) stk.pop();if (!stk.empty()) cout << stk.top() << ' ';else cout << -1 << ' ';stk.push(x);}return 0;
}

2.2 单调栈模板题II

原题链接:洛谷 P5788 【模板】单调栈

此题对应情形四,不再赘述,直接给出AC代码。

#include <bits/stdc++.h>using namespace std;const int N = 3e6 + 10;int a[N], ans[N];
stack<int> stk;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = n; i; i--) {while (!stk.empty() && a[stk.top()] <= a[i]) stk.pop();if (!stk.empty()) ans[i] = stk.top();stk.push(i);}for (int i = 1; i <= n; i++) cout << ans[i] << ' ';return 0;
}

2.3 Bad Hair Day

原题链接:POJ 3250 Bad Hair Day

本题类似于情形四,但还是有些区别。

首先应当注意到以下几点:

  • 每头牛只向右看;
  • 每头牛只能看见比自己低的牛的头顶;
  • 若两头牛一样高,则它们互相看不到对方的头顶。

这相当于对数组中的某个数,我们要在它的右边寻找第一个大于等于它的数的下标,知道下标后,我们就可以算出这头牛能够看到多少头牛的头顶了。当然,如果找不到这样的数,说明这头牛的右边全是比它低的牛,此时用牛的数量减去该牛的下标(从 111 开始)就是该牛能够看到的头顶的数量。

还需注意一个问题,假设给定的序列是单调递减的,那么所求答案为 N(N−1)/2≈3×109N(N-1)/2\approx 3\times 10^9N(N1)/23×109,会爆 int

#include <stack>using namespace std;typedef long long LL;LL h[80010], ans;
stack<LL> stk;int main() {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%lld", &h[i]);for (int i = n; i; i--) {while (!stk.empty() && h[stk.top()] < h[i]) stk.pop();if (!stk.empty()) ans += stk.top() - i - 1;else ans += n - i;stk.push(i);}printf("%lld", ans);return 0;
}

三、单调队列

单调队列的定义类似于单调栈:

  • 单调递增队列: 从队尾到队头单调递增;
  • 单调递减队列: 从队尾到队头单调递减。

例如对于单调递增队列,向其中插入元素的时候,为了维护队列的单调性,需要在保证将该元素插入到队尾后整个队列满足单调性的前提下弹出最少的元素(从队尾弹出):

deque<int> q;  // 因为涉及到从队尾弹出,所以只能用双端队列来实现单调队列void insert(int x) {while (!q.empty() && q.back() < x)q.pop_back();q.push_back(x);
}

⚠️ 严格意义上讲单调队列并不是队列,因为它不满足FIFO。

四、单调队列的应用

4.1 滑动窗口

原题链接:AcWing 154. 滑动窗口

单调队列常用于求滑动窗口中的最大(小)值。我们先来看一下这道题的暴力解法是什么样的:

#include <bits/stdc++.h>using namespace std;typedef long long LL;const LL INF = 3e9;LL a[1000010];int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n, k;cin >> n >> k;for (int i = 0; i < n; i++) cin >> a[i];for (int i = 0; i <= n - k; i++) {LL mini = INF;for (int j = i; j < i + k; j++) mini = min(mini, a[j]);cout << mini << ' ';}cout << "\n";for (int i = 0; i <= n - k; i++) {LL maxi = -INF;for (int j = i; j < i + k; j++) maxi = max(maxi, a[j]);cout << maxi << ' ';}return 0;
}

显然时间复杂度为 O(nk)O(nk)O(nk),基本会TLE。使用单调队列,我们可以将时间复杂度降低至 O(n)O(n)O(n)

以求最小值为例,我们使用单调递减队列来保存滑动窗口中的元素,下标越大的元素越接近队尾,下标越小的元素越接近队头,于是求滑动窗口中的最小值相当于访问队头元素。

不妨设下标从 111 开始,初始时 iii 指向 111,并且 iii111 遍历至 nnn,每次遍历都将 a[i]a[i]a[i] 插入到队列中。可以发现,只要有 i<ki< ki<k 就说明滑动窗口还未形成,此时无需输出最小值,当 i≥ki\geq kik 时才需要输出最小值。

那何时弹出队头元素呢?不妨让单调队列保存的是元素的下标而非元素本身,设当前窗口为 a[i−k..i−1]a[i-k..i-1]a[ik..i1],向单调队列插入 iii 后,新窗口变成 a[i−k+1..i]a[i-k+1..i]a[ik+1..i],如果队头元素小于等于 i−ki-kik,说明最小值不在新窗口中,此时应当弹出队头元素。

到目前为止,可以总结出两点:

  • 只要 i≥ki\geq kik 就应当输出队头元素;
  • 当队头元素小于等于 i−ki-kik 时,弹出队头元素。
#include <bits/stdc++.h>using namespace std;const int N = 1e6 + 10;int a[N];
deque<int> q;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n, k;cin >> n >> k;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= n; i++) {while (!q.empty() && a[q.back()] > a[i]) q.pop_back();q.push_back(i);if (!q.empty() && q.front() <= i - k) q.pop_front();  // 既可以用if也可以用whileif (i >= k) cout << a[q.front()] << ' ';}cout << "\n";q.clear();for (int i = 1; i <= n; i++) {while (!q.empty() && a[q.back()] < a[i]) q.pop_back();q.push_back(i);if (!q.empty() && q.front() <= i - k) q.pop_front();  // 既可以用if也可以用whileif (i >= k) cout << a[q.front()] << ' ';}return 0;
}

此题还可以用优先队列来做,为避免不必要的判断,我们可以在一开始就向队列中插入前 kkk 个元素:

#include <bits/stdc++.h>using namespace std;typedef pair<int, int> PII;
const int N = 1e6 + 10;int a[N];
priority_queue<PII> p;  // 大根堆
priority_queue<PII, vector<PII>, greater<>> q;  // 小根堆int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n, k;cin >> n >> k;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= k; i++) q.emplace(a[i], i);cout << q.top().first << ' ';for (int i = k + 1; i <= n; i++) {q.emplace(a[i], i);while (!q.empty() && q.top().second <= i - k) q.pop();  // 只能用whilecout << q.top().first << ' ';}cout << "\n";for (int i = 1; i <= k; i++) p.emplace(a[i], i);cout << p.top().first << ' ';for (int i = k + 1; i <= n; i++) {p.emplace(a[i], i);while (!p.empty() && p.top().second <= i - k) p.pop();  // 只能用whilecout << p.top().first << ' ';}return 0;
}

观察上面两段代码,可以发现思路是大致相同的,但区别在于(请看注释行),单调队列在弹出队头元素的时候既可以用 if 也可以用 while,而优先队列弹出队头元素的时候只能用 while,这是为什么呢?

考虑数组 [4,6,2,3,5,1,8,7,9][4,6,2,3,5,1,8,7,9][4,6,2,3,5,1,8,7,9],窗口大小为 333,以求最小值为例,每次循环结束后单调队列和优先队列的状态列在下表中:

⚠️ 列表的左端是队头,右端是队尾。
⚠️ 对于优先队列,只需看列表的第一个元素,其后元素的次序无关紧要。

滑动窗口单调队列优先队列
[4,6,2][4,6,2][4,6,2][2][2][2][2,4,6][2,4,6][2,4,6]
[6,2,3][6,2,3][6,2,3][2,3][2,3][2,3][2,4,6,3][2,4,6,3][2,4,6,3]
[2,3,5][2,3,5][2,3,5][2,3,5][2,3,5][2,3,5][2,4,6,3,5][2,4,6,3,5][2,4,6,3,5]
[3,5,1][3,5,1][3,5,1][1][1][1][1,2,4,6,3,5][1,2,4,6,3,5][1,2,4,6,3,5]
[5,1,8][5,1,8][5,1,8][1,8][1,8][1,8][1,2,4,6,3,5,8][1,2,4,6,3,5,8][1,2,4,6,3,5,8]
[1,8,7][1,8,7][1,8,7][1,7][1,7][1,7][1,2,4,6,3,5,8,7][1,2,4,6,3,5,8,7][1,2,4,6,3,5,8,7]
[8,7,9][8,7,9][8,7,9][7,9][7,9][7,9][7,8,9][7,8,9][7,8,9]

因为优先队列在插入元素的过程中不会弹出元素,所以只要队头位于窗口内,那么优先队列的大小只增不减,这就导致优先队列中存在许多冗余元素(不属于窗口内的元素)。而单调队列为了保持单调性,插入元素的时候会从队尾弹出一些元素,这就保证了单调队列中的元素始终是滑动窗口中的元素的子集,因此单调队列的 while 循环至多执行一次,自然可以改成 if

相关文章:

深入浅出单调栈与单调队列

目录一、单调栈情形一&#xff1a;寻找一个数左边第一个小于它的数情形二&#xff1a;寻找一个数左边第一个小于它的数的下标情形三&#xff1a;寻找一个数右边第一个大于它的数情形四&#xff1a;寻找一个数右边第一个大于它的数的下标二、单调栈的应用2.1 单调栈模板题I2.2 单…...

深入C语言——实现可变参数函数

文章目录初步示例函数解析最大值函数初步示例 stdarg.h提供了C语言对可变参数的支持&#xff0c;先举一个简短的例子 //testStdArg.c #include <stdarg.h> #include <stdio.h>void printIntList(int N, ...){va_list args; //存放...所代表的参数va_start(…...

41-Dockerfile-Dockerfile简介

Dockerfile简介前言Dockerfile 简介基础知识使用Dockerfile 构建镜像步骤Dockerfile 构建过程Dockerfile基本结构Dockerfile示例总结前言 本篇开始来学习下Dockerfile相关的用法 Dockerfile 简介 Dockerfile : 是用来构建 Docker 镜像的文本文件&#xff0c;是有一条条构建镜…...

【408】操作系统 - 刻骨铭心自测题1(上)

文章目录OS练习题第一部分&#xff1a;1&#xff1a;2&#xff1a;3&#xff1a;4&#xff1a;5&#xff1a;6&#xff1a;7&#xff1a;8&#xff1a;9&#xff1a;10&#xff1a;11&#xff1a;12&#xff1a;13&#xff1a;14&#xff1a;15&#xff1a;16&#xff1a;17&am…...

【老卫拆书】009期:Vue+Node肩挑全栈!《Node.js+Express+MongoDB+Vue.js全栈开发实战》开箱

今天刚拿到一本新书&#xff0c;叫做《Node.jsExpressMongoDBVue.js全栈开发实战》&#xff0c;做个开箱。 外观 先从外观上讲&#xff0c;这本是全新的未开封的&#xff0c;膜还在。 这本书介绍从技术原理到整合开发实战&#xff0c;以丰富的项目展现全栈开发的一个技巧。 …...

【LeetCode】动态规划总结

动态规划解决的问题 动态规划和贪心的区别&#xff1a; 动态规划是由前一个状态推导出来的&#xff1b; 贪心是局部直接选最优的。 动态规划解题步骤 状态定义&#xff1a;确定dp数组以及下标的含义状态转移方程&#xff1a;确定递推公式初始条件&#xff1a;dp如何初始化遍历…...

CAS详解.

CAS这个机制就给实现线程安全版本的代码&#xff0c;提供了一个新的思路&#xff0c;之前通过加锁&#xff0c;把多个指令打包成整体&#xff0c;来实现线程安全。现在就可以考虑直接基与CAS来实现一些修改操作&#xff0c;也能保证线程安全&#xff08;不需要加锁&#xff09;…...

Mock.js初步使用(浏览器端)

Mock.js&#xff1a;生成随机数据&#xff0c;拦截 Ajax 请求。官方地址&#xff1a;http://mockjs.com/第一个demodemo.html<!DOCTYPE html> <html> <head><meta charset"utf-8"><title>mockjs demo</title> </head> <…...

opencv保存图片

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

【c++】数据类型

文章目录整型实型科学计数法sizeof关键字字符型字符串类型转义字符bool布尔类型c规定在创建一个变量或者常量时&#xff0c;必须要指定出相应的数据类型&#xff0c;否则无法给变量分配内存。 整型 作用&#xff1a;整型变量表示的是整数类型的数据。 实型 float f3.14; //默…...

Elasticsearch的写的底层原理

前面有一篇文章讲解了Elasticsearch的读写搜索过程&#xff0c;有的人感觉不太理解&#xff0c;今天我们再来看看这些过程的原理 写数据底层原理 首先是将数据写入到内存buffer中&#xff0c;在这里的时候&#xff0c;数据是搜索不到。他同时会将数据写入到translog日志文件中…...

【网络编程】Java中的Socket

文章目录前言socket是什么&#xff1f;Java中的SocketJava实现网络上传文件前言 所谓Socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用…...

有趣的Hack-A-Sat黑掉卫星挑战赛——跟踪卫星

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…...

Ubuntu安装配置Cuda和Pytorch gpu

前言 在Ubuntu中操作系统中,通过Anconda安装对应的虚拟环境以及软件包,一般都需要适配Cuda、Pytorch版本等 以下安装配置都是在Ubuntu操作系统下 1. 安装Cuda 通过Ubuntu操作系统查看cuda适配的版本:nvidia-smi 截图如下: 查看Ubuntu版本可如下方式 (1)cat /proc/ver…...

三、Java面向对象

1 . 方法 方法(method)是程序中最小的执行单元方法就是一些代码的打包 需要的时候可以直接调用方法之间是平级的关系 不能在方法里面定义方法方法不调用就不执行 方法的定义 // 方法的定义 /* [修饰符] 返回值类型 方法名称([参数 1],[参数 2]){语句A;return 返回值; } *///…...

pygame7 弹球游戏2

上节课我们做到当球静止下来后在第0号球上画一个球杆 本节课我们将会让这个球杆将球打出来 1、鼠标事件 pygame.mouse.get_pressed():返回鼠标左键&#xff0c;中间&#xff0c;右键的情况 2、键盘事件&#xff1a; pygame.key.get_pressed(): 返回所有键盘的情况 3、pyg…...

计算机网络4:计算机网络体系结构

目录计算机网络体系结构1.网络模型2.每一层的代表含义2.1 OSI7层模型2.2 五层协议2.3 TCP/IP 四层协议3.数据在各层之间的传输过程4.为什么要进行分层计算机网络体系结构 1.网络模型 2.每一层的代表含义 2.1 OSI7层模型 &#xff08;1&#xff09;物理层&#xff1a;比特流–…...

1630_GNU的二进制分析工具nm简单使用探索

全部学习汇总&#xff1a; GreyZhang/toolbox: 常用的工具使用查询&#xff0c;非教程&#xff0c;仅作为自我参考&#xff01; (github.com) GNU有一套二进制的分析工具&#xff0c;之前是用过objdump的&#xff0c;但是也没有系统掌握。如果做底层软件的设计&#xff0c;这些…...

【Redis】Redis高可用之Redis Cluster集群模式详解(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建工设优化。文章内容兼具广度深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于知名金融公…...

1.8 正则表达式

正则表示式是用来匹配与查找字符串的&#xff0c;从网上爬取数据不可避免的会用到正则表达式。 Python 的表达式要先引入 re 模块&#xff0c;正则表达式以 r 引导。Re库主要功能函数函数说明re.search()在一个字符串中搜索匹配正则表达式的第一个位置&#xff0c;返回match对象…...

Postgresql 根据单列或几列分组去重row_number() over() partition by

Postgresql 根据单列或几列分组去重row_number() over() partition by 一般用于单列或者几列需要去重后进行计算值的 count(distinct(eid)) 可以 比如有个例子&#xff0c;需要根据名称&#xff0c;城市去筛选覆盖的道路长度&#xff0c;以月因为建立了唯一索引是ok的&#…...

基于蒙特卡洛法的规模化电动车有序充放电及负荷预测(PythonMatlab实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;主要研究方向是电力系统和智能算法、机器学…...

Selenium常用API详解,从入门到进阶(全套)

目录 1、打开页面 2、查找页面元素 3、输入文本 4、点击操作 5、提交操作 6、清除文本 7、获取文本、属性 8、获取页面的标题和URL 9、窗口 9.1、设置窗口大小 9.2、窗口切换 9.2.1、为什么需要窗口切换&#xff1f; 9.2.2、获取句柄的方式 9.2.3、切换句柄 10、…...

自从学会了Python,我实现了壁纸自由(6)

小朋友们好&#xff0c;大朋友们好&#xff01;我是猫妹&#xff01;哈哈哈&#xff0c;又到周末啦&#xff01;这周过得怎么样&#xff1f;马上就要开学了&#xff0c;寒假作业早已写好了吧&#xff1f;开学让人兴奋&#xff0c;上了很久网课都要吐啦&#xff01;开学也让人有…...

Ruby 发送邮件 - SMTP

SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则&#xff0c;由它来控制信件的中转方式。 Ruby提供了 Net::SMTP 来发送邮件&#xff0c;并提供了两个方法 new 和 start: new 方法有两个参数&am…...

Python爱心代码

前言 Python漂浮爱心&#xff0c;具体源码见&#xff1a;Python动态爱心代码_爱心代码-Python文档类资源-CSDN下载 爱心类 class Heart(): #每个爱心&#xff08;爱心类&#xff09; def __init__(self): self.r ra.randint(10,15) #爱心的半径 …...

【二分查找法及其应用】

文章目录一. 前提二. 基本思路三. 代码实现四. 封装在STL中的二分查找算法五. 浮点数二分一. 前提 待查找的序列是有序的&#xff1b;待查找的 a 采取顺序存储结构。 二. 基本思路 设在升序序列 a [ low…high ] 查找的 k &#xff0c; 首先找中间值 mid a [ ( lowhigh )/2 …...

Android 进阶——Framework核心 之Binder Java成员类详解(三)

文章大纲引言一、Binder Java家族核心成员关系图二、Binder Java家族核心成员源码概述1、android.os.IBinder1.1、boolean transact(int code, Parcel data, Parcel reply, int flags) send a call to an IBinder object1.2、String getInterfaceDescriptor()1.3、boolean ping…...

Maven

Maven 1.什么是Maven 官方网站 https://maven.apache.org/ Maven是一款服务于Java平台的自动化构建工具&#xff0c;它可以帮助我们更方便的对项目进行构建、管理项目jar包 &#xff0c;包括: bulid 项目&#xff0c;切换 jar 版本&#xff0c;添加 jar, 删除 jar 包等 1.…...

1947抓住那头牛(队列 广度优先搜索)

目录 题目描述 解析 解题思路 代码部分 代码部分 运行结果 看看len数组中各个位置的标记值 为什么这样做一定是最短路径&#xff1a; 题目描述 农夫知道一头牛的位置&#xff0c;想要抓住它。农夫和牛都位于数轴上&#xff0c;农夫起始位于点N(0<N<100000)&…...

广西建设厅官网站/网络营销常见术语

续Struts2_源码学习_init() Logging System 首先&#xff0c;我们可以先看一下init.initLogging(config)这个操作&#xff0c;查看源码你会发现这里用了反射、伪单例模式&#xff08;仔细看源码其实不符合单例模式的要求&#xff0c;可以创建多个实例&#xff09;、工厂模式、…...

网站架构模式用哪种/中国突然宣布一重磅消息

什么是语法解析&#xff1f; 在自然语言学习过程中&#xff0c;每个人一定都学过语法&#xff0c;例如句子可以用主语、谓语、宾语来表示。在自然语言的处理过程中&#xff0c;有许多应用场景都需要考虑句子的语法&#xff0c;因此研究语法解析变得非常重要。语法解析有两个主要…...

如何将网站做的更美观/广告联盟哪个比较好

文章目录从算法的角度&#xff0c;拉普拉斯的计算需要两张相邻分辨率的高斯图像&#xff08;3840x2160,1920x1080)&#xff0c;拉普拉斯对低分辨率的图像做上采样、填充之后卷积&#xff0c;然后和高分辨率的图像相减&#xff0c;得到残差&#xff08;边缘纹理&#xff09;。 …...

政府门户网站群建设方案/仓山区seo引擎优化软件

现在小学的数学题目也不是那么好玩的。 看看这个寒假作业&#xff1a; □ □ □ □ - □ □ □ □ □ □ □ □ (如果显示不出来&#xff0c;可以参见【图1.jpg】) 每个方块代表1~13中的某一个数字&#xff0c;但不能重复。 比如&#xff1a; 6 …...

建设电商网站/企业网站快速排名

结构化分析方法的概念 结构化分析模型 结构化分析过程...

360报危险网站/必应搜索引擎首页

C语言基础学习PYTHON——基础学习D1720181014内容纲要&#xff1a;1、jQuery介绍2、jQuery功能介绍(1)jQuery的引入方式(2)选择器(3)筛选(4)文本操作(5)样式操作(6)属性操作(7)文本处理(8)css处理(9)位置(10)事件(11)jQuery扩展3、实例展示4、小结5、推荐1 jQuery介绍jQuery是一…...