国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

十堰的網(wǎng)站建設(shè)杭州seook優(yōu)屏網(wǎng)絡(luò)

十堰的網(wǎng)站建設(shè),杭州seook優(yōu)屏網(wǎng)絡(luò),做書籍封皮的網(wǎng)站,買微信公眾號多少錢一個目錄一、單調(diào)棧情形一:尋找一個數(shù)左邊第一個小于它的數(shù)情形二:尋找一個數(shù)左邊第一個小于它的數(shù)的下標(biāo)情形三:尋找一個數(shù)右邊第一個大于它的數(shù)情形四:尋找一個數(shù)右邊第一個大于它的數(shù)的下標(biāo)二、單調(diào)棧的應(yīng)用2.1 單調(diào)棧模板題I2.2 單…

目錄

  • 一、單調(diào)棧
    • 情形一:尋找一個數(shù)左邊第一個小于它的數(shù)
    • 情形二:尋找一個數(shù)左邊第一個小于它的數(shù)的下標(biāo)
    • 情形三:尋找一個數(shù)右邊第一個大于它的數(shù)
    • 情形四:尋找一個數(shù)右邊第一個大于它的數(shù)的下標(biāo)
  • 二、單調(diào)棧的應(yīng)用
    • 2.1 單調(diào)棧模板題I
    • 2.2 單調(diào)棧模板題II
    • 2.3 Bad Hair Day
  • 三、單調(diào)隊列
  • 四、單調(diào)隊列的應(yīng)用
    • 4.1 滑動窗口

一、單調(diào)棧

所謂單調(diào)棧,就是指滿足單調(diào)性的棧結(jié)構(gòu):

  • 單調(diào)遞增棧: 棧中元素從棧底到棧頂是遞增的;
  • 單調(diào)遞減棧: 棧中元素從棧底到棧頂是遞減的。

例如對于單調(diào)遞增棧,向其中插入元素的時候,為了維護(hù)棧的單調(diào)性,需要在保證將該元素插入到棧頂后整個棧滿足單調(diào)性的前提下彈出最少的元素:

stack<int> stk;void insert(int x) {while (!stk.empty() && stk.top() > x)  // 當(dāng)stk.top() <= x時滿足單調(diào)性stk.pop();stk.push(x);
}

單調(diào)??梢杂脕碓谝粋€數(shù)組中尋找某一個元素左邊(或右邊)第一個大于(或小于或大于等于或小于等于)它的元素(或元素的下標(biāo))。

這句話看起來有些繞,接下來我們只考慮以下四種「基本情形」。

情形一:尋找一個數(shù)左邊第一個小于它的數(shù)

給定一個長度為 n(≤105)n\,(\leq 10^5)n(105) 的數(shù)組 aaa,輸出每個數(shù)左邊第一個比它小的數(shù),如果不存在則輸出 ?1-1?1。

傳統(tǒng)的暴力做法是雙重循環(huán):

#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;
}

然而這種做法的復(fù)雜度是 O(n2)O(n^2)O(n2),利用單調(diào)棧,我們可以將復(fù)雜度降低至 O(n)O(n)O(n)

在指針 iii 從左往右遍歷的過程中,我們可以用一個棧來保存 iii 左邊的所有元素(不包括 iii 指向的元素),下標(biāo)越大的元素越接近棧頂,下標(biāo)越小的元素越接近棧底。

每次我們訪問棧頂,只要棧頂元素大于等于 a[i]a[i]a[i],我們就將棧頂元素彈出,直至棧頂元素小于 a[i]a[i]a[i],此時輸出棧頂元素并將 a[i]a[i]a[i] 壓入棧中。 由于棧中保存了 iii 左邊的所有元素,所以只要有答案,則答案一定在棧中。

📃 對證明不感興趣的讀者可以跳過這部分
講到這里,可能會有讀者好奇,棧不是一直在彈出元素嗎,萬一先前就把答案彈出去了怎么辦?這里我們可以從數(shù)學(xué)的角度進(jìn)行證明。假設(shè)對于 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,?,i?1?

對于第二個約束,假設(shè)有某個 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] 才應(yīng)該是答案,矛盾!

下面證明,當(dāng)指針指向 a[i]a[i]a[i] 時,a[p]a[p]a[p] 一定存在于棧中。
當(dāng)指針指向 a[p]a[p]a[p] 時,這一輪循環(huán)結(jié)束后,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,i?1],當(dāng)指針指向 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] 所以不會被彈出,自然地,當(dāng)指針指向 a[i]a[i]a[i] 時,a[p]a[p]a[p] 仍在棧中。

由于每個元素一定會被壓入一次且至多彈出一次,因此操作次數(shù)至多是 2n2n2n,故總時間復(fù)雜度為 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;
}

📝 代碼完全可以簡化,之所以這么寫是為了方便統(tǒng)一格式。

情形二:尋找一個數(shù)左邊第一個小于它的數(shù)的下標(biāo)

和情形一類似,只不過這里我們尋找的是下標(biāo),如果不存在則輸出 ?1-1?1。

只需對棧做一點小小的修改就能應(yīng)對情形二。注意到之前我們尋找的是元素所以讓棧去保存元素,現(xiàn)在我們尋找下標(biāo),所以讓棧去保存元素的下標(biāo)就可以了。

#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;
}

情形三:尋找一個數(shù)右邊第一個大于它的數(shù)

之前我們是在一個數(shù)的左邊去尋找,所以讓棧去保存這個數(shù)左邊的所有數(shù),類似地,現(xiàn)在需要讓棧去保存這個數(shù)右邊的所有數(shù)。

考慮將數(shù)組翻轉(zhuǎn)(實際上不可能翻轉(zhuǎn),而是倒序遍歷),因此情形三變成了「尋找一個數(shù)左邊第一個大于它的數(shù)」,于是歸結(jié)為情形一。

#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;
}

情形四:尋找一個數(shù)右邊第一個大于它的數(shù)的下標(biāo)

結(jié)合情形二和情形三即可得出。

#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;
}

不難發(fā)現(xiàn),這四種情形只在第 16,17,2016,17,2016,17,20 行不同,其余部分的代碼均相同,據(jù)此可以總結(jié)出以下三點區(qū)別:

  • 遍歷順序(以怎樣的順序遍歷數(shù)組 aaa);
  • 比較方式(如何比較當(dāng)前元素和棧頂元素);
  • 棧中存儲的是什么(是元素本身還是元素的下標(biāo)還是其他)。

二、單調(diào)棧的應(yīng)用

2.1 單調(diào)棧模板題I

原題鏈接:AcWing 830. 單調(diào)棧

此題對應(yīng)情形一,不再贅述,直接給出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 單調(diào)棧模板題II

原題鏈接:洛谷 P5788 【模板】單調(diào)棧

此題對應(yīng)情形四,不再贅述,直接給出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

本題類似于情形四,但還是有些區(qū)別。

首先應(yīng)當(dāng)注意到以下幾點:

  • 每頭牛只向右看;
  • 每頭牛只能看見比自己低的牛的頭頂;
  • 若兩頭牛一樣高,則它們互相看不到對方的頭頂。

這相當(dāng)于對數(shù)組中的某個數(shù),我們要在它的右邊尋找第一個大于等于它的數(shù)的下標(biāo),知道下標(biāo)后,我們就可以算出這頭牛能夠看到多少頭牛的頭頂了。當(dāng)然,如果找不到這樣的數(shù),說明這頭牛的右邊全是比它低的牛,此時用牛的數(shù)量減去該牛的下標(biāo)(從 111 開始)就是該牛能夠看到的頭頂?shù)臄?shù)量。

還需注意一個問題,假設(shè)給定的序列是單調(diào)遞減的,那么所求答案為 N(N?1)/2≈3×109N(N-1)/2\approx 3\times 10^9N(N?1)/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;
}

三、單調(diào)隊列

單調(diào)隊列的定義類似于單調(diào)棧:

  • 單調(diào)遞增隊列: 從隊尾到隊頭單調(diào)遞增;
  • 單調(diào)遞減隊列: 從隊尾到隊頭單調(diào)遞減。

例如對于單調(diào)遞增隊列,向其中插入元素的時候,為了維護(hù)隊列的單調(diào)性,需要在保證將該元素插入到隊尾后整個隊列滿足單調(diào)性的前提下彈出最少的元素(從隊尾彈出):

deque<int> q;  // 因為涉及到從隊尾彈出,所以只能用雙端隊列來實現(xiàn)單調(diào)隊列void insert(int x) {while (!q.empty() && q.back() < x)q.pop_back();q.push_back(x);
}

?? 嚴(yán)格意義上講單調(diào)隊列并不是隊列,因為它不滿足FIFO。

四、單調(diào)隊列的應(yīng)用

4.1 滑動窗口

原題鏈接:AcWing 154. 滑動窗口

單調(diào)隊列常用于求滑動窗口中的最大(小)值。我們先來看一下這道題的暴力解法是什么樣的:

#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;
}

顯然時間復(fù)雜度為 O(nk)O(nk)O(nk),基本會TLE。使用單調(diào)隊列,我們可以將時間復(fù)雜度降低至 O(n)O(n)O(n)。

以求最小值為例,我們使用單調(diào)遞減隊列來保存滑動窗口中的元素,下標(biāo)越大的元素越接近隊尾,下標(biāo)越小的元素越接近隊頭,于是求滑動窗口中的最小值相當(dāng)于訪問隊頭元素。

不妨設(shè)下標(biāo)從 111 開始,初始時 iii 指向 111,并且 iii111 遍歷至 nnn,每次遍歷都將 a[i]a[i]a[i] 插入到隊列中??梢园l(fā)現(xiàn),只要有 i<ki< ki<k 就說明滑動窗口還未形成,此時無需輸出最小值,當(dāng) i≥ki\geq kik 時才需要輸出最小值。

那何時彈出隊頭元素呢?不妨讓單調(diào)隊列保存的是元素的下標(biāo)而非元素本身,設(shè)當(dāng)前窗口為 a[i?k..i?1]a[i-k..i-1]a[i?k..i?1],向單調(diào)隊列插入 iii 后,新窗口變成 a[i?k+1..i]a[i-k+1..i]a[i?k+1..i],如果隊頭元素小于等于 i?ki-ki?k,說明最小值不在新窗口中,此時應(yīng)當(dāng)彈出隊頭元素。

到目前為止,可以總結(jié)出兩點:

  • 只要 i≥ki\geq kik 就應(yīng)當(dāng)輸出隊頭元素;
  • 當(dāng)隊頭元素小于等于 i?ki-ki?k 時,彈出隊頭元素。
#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;
}

此題還可以用優(yōu)先隊列來做,為避免不必要的判斷,我們可以在一開始就向隊列中插入前 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;
}

觀察上面兩段代碼,可以發(fā)現(xiàn)思路是大致相同的,但區(qū)別在于(請看注釋行),單調(diào)隊列在彈出隊頭元素的時候既可以用 if 也可以用 while,而優(yōu)先隊列彈出隊頭元素的時候只能用 while,這是為什么呢?

考慮數(shù)組 [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,以求最小值為例,每次循環(huán)結(jié)束后單調(diào)隊列和優(yōu)先隊列的狀態(tài)列在下表中:

?? 列表的左端是隊頭,右端是隊尾。
?? 對于優(yōu)先隊列,只需看列表的第一個元素,其后元素的次序無關(guān)緊要。

滑動窗口單調(diào)隊列優(yōu)先隊列
[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]

因為優(yōu)先隊列在插入元素的過程中不會彈出元素,所以只要隊頭位于窗口內(nèi),那么優(yōu)先隊列的大小只增不減,這就導(dǎo)致優(yōu)先隊列中存在許多冗余元素(不屬于窗口內(nèi)的元素)。而單調(diào)隊列為了保持單調(diào)性,插入元素的時候會從隊尾彈出一些元素,這就保證了單調(diào)隊列中的元素始終是滑動窗口中的元素的子集,因此單調(diào)隊列的 while 循環(huán)至多執(zhí)行一次,自然可以改成 if。

http://aloenet.com.cn/news/46240.html

相關(guān)文章:

  • wordpress https 網(wǎng)站分享企業(yè)網(wǎng)站建設(shè)方案范文
  • 制作一個網(wǎng)站需要多少錢百度托管公司
  • 手機(jī)網(wǎng)站在哪里找到外貿(mào)推廣平臺排名
  • wordpress 前端展示seopeixun
  • 做網(wǎng)站的計劃書有哪些免費推廣軟件
  • 微信群如何推廣網(wǎng)站建設(shè)站長之家seo綜合查詢
  • 上海建筑工程網(wǎng)seo視頻教程百度云
  • 深圳網(wǎng)站托管公司谷歌seo新規(guī)則
  • 松江泗涇網(wǎng)站建設(shè)查看關(guān)鍵詞被搜索排名的軟件
  • 如何建立網(wǎng)站的步驟加強服務(wù)保障滿足群眾急需ruu7
  • app開發(fā)技術(shù)東莞快速優(yōu)化排名
  • 100款免費軟件網(wǎng)站大全亞馬遜的免費網(wǎng)站
  • 青海旅游的網(wǎng)站建設(shè)搜索引擎下載
  • 深圳昊客網(wǎng)絡(luò)推廣寧波seo優(yōu)化公司排名
  • xxx網(wǎng)站建設(shè)規(guī)劃域名注冊信息查詢whois
  • 阿里媽媽 網(wǎng)站建設(shè)不完整長沙網(wǎng)絡(luò)優(yōu)化產(chǎn)品
  • py可以做網(wǎng)站嗎西安seo優(yōu)化顧問
  • 小組做數(shù)據(jù)庫網(wǎng)站成都網(wǎng)站快速排名
  • 網(wǎng)站建設(shè)建設(shè)營銷策略的重要性
  • 長沙專業(yè)網(wǎng)站制作seo推廣具體做什么
  • 建設(shè)購物網(wǎng)站廣告收益平臺
  • 微網(wǎng)站開發(fā)技術(shù)架構(gòu)競價推廣運營
  • 順德大良網(wǎng)站建設(shè)開發(fā)海南百度推廣seo
  • 網(wǎng)站能獲取訪問者亞馬遜站外推廣網(wǎng)站
  • 彩票網(wǎng)站開發(fā)合法嗎淄博頭條新聞今天
  • linux系統(tǒng)怎么做網(wǎng)站網(wǎng)站建設(shè)營銷推廣
  • 網(wǎng)站怎么做qq客服seo搜索引擎招聘
  • 新疆網(wǎng)站建設(shè)大全今日軍事新聞視頻
  • 綏化市建設(shè)局網(wǎng)站app推廣平臺放單平臺
  • 網(wǎng)站做目錄交換友情鏈接的渠道