一、設計一個最小棧
題目要求:設計一個支持push,pop,top等操作並且可以在時間內檢索出最小元素的堆棧。
- push(x)–將元素x插入棧中
- pop()–移除棧頂元素
- top()–得到棧頂元素
- getMin()–得到棧中最小元素
樣例:
MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getMin(); --> Returns -1.
來源:劍指offer
思路:主要是完成最後一個功能getMin()–得到棧中最小元素,其他三個功能正常棧中都有。可以用兩個棧來完成,一個棧是正常的棧,另一個保存前i個數的最小值。例如原棧stack<int> s={-1, 3, -4}
,則輔助棧stack<int> t={-1, -1, -4}
。
- 當插入時,
s={-1, 3, -4, x}, t = {-1, -1, -4, min(x, -4)}
- 當移除棧頂元素時將
s,t
棧頂元素出棧即可 - 得到棧中最小元素時返回輔助棧的棧頂元素即可
程序(CPP):
class MinStack {
public:
/** initialize your data structure here. */
stack<int> s, t;
MinStack() {
}
void push(int x) {
s.push(x);
if(!t.empty()) t.push(min(t.top(), x));
else t.push(x);
}
void pop() {
s.pop();
t.pop();
}
int top() {
return s.top();
}
int getMin() {
return t.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
二、編輯器
題目要求:你將要實現一個功能強大的整數序列編輯器。在開始時,序列是空的。編輯器共有五種指令,如下:
- 1、“I x”,在光標處插入數值x。
- 2、“D”,將光標前面的第一個元素刪除,如果前面沒有元素,則忽略此操作。
- 3、“L”,將光標向左移動,跳過一個元素,如果左邊沒有元素,則忽略此操作。
- 4、“R”,將光標向右移動,跳過一個元素,如果右邊沒有元素,則忽略次操作。
- 5、“Q k”,假設此刻光標之前的序列爲a1,a2,…,an,輸出max1≤i≤kSi,其中Si=a1+a2+…+ai。
輸入格式:
第一行包含一個整數Q,表示指令的總數。
接下來Q行,每行一個指令,具體指令格式如題目描述。
輸出格式:
每一個“Q k”指令,輸出一個整數作爲結果,每個結果佔一行。
數據範圍:
輸入樣例:
8
I 2
I -1
I 1
Q 3
L
D
R
Q 2
輸出樣例:
2
3
思路:用兩個棧來維護:左邊的棧sl
和右邊的棧sr
。
- 當插入一個元素的時候相當於左邊的棧
sl
插入一個元素,同時需要更新前綴和數組s
及前綴和數組的前i個數的最小值數組f
- 當刪除一個元素時相當於刪除左邊的棧
sl
的棧頂元素 - 當向左移動時相當於左邊的棧
sl
的棧頂元素出棧,並將該元素放入右邊的棧sr
入棧 - 當向右移動時時同理
- 查詢時輸出前綴和數組的前i個數的最小值數組
f
對應位置的值即可
程序(CPP):
#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;
const int maxn = 1e6+10;
int sl[maxn], sr[maxn], tl, tr; // 左右兩個棧及其棧頂位置
int s[maxn], f[maxn]; // 前綴和數組和保留前i個數的最小值
void push_l(int x)
{
sl[++tl] = x;
s[tl] = s[tl-1]+x;
f[tl] = max(f[tl-1], s[tl]);
}
int main()
{
int Q; cin >> Q;
f[0] = INT_MIN;
while(Q--)
{
char t; cin >> t;
int x;
if(t=='I')
{
cin >> x;
push_l(x);
}
else if(t=='D')
{
if(tl>0) tl--;
}
else if(t=='L')
{
if(tl>0) sr[++tr] = sl[tl--];
}
else if(t=='R')
{
if(tr>0) push_l(sr[tr--]);
}
else if(t=='Q')
{
cin >> x;
cout << f[x] << endl;
}
}
return 0;
}
三、火車進棧
題目描述:
這裏有n列火車將要進站再出站,但是,每列火車只有1節,那就是車頭。
這n列火車按1到n的順序從東方左轉進站,這個車站是南北方向的,它雖然無限長,只可惜是一個死衚衕,而且站臺只有一條股道,火車只能倒着從西方出去,而且每列火車必須進站,先進後出。
也就是說這個火車站其實就相當於一個棧,每次可以讓右側頭火車進棧,或者讓棧頂火車出站。
輸入格式
輸入一個整數n,代表火車數量。
輸出格式
按照《字典序》輸出前20種答案,每行一種,不要空格。
數據範圍
輸入樣例:
3
輸出樣例:
123
132
213
231
321
思路1:
因爲只需要枚舉20種情況,因此可以枚舉出前n個數的排列組合再依次判斷每種組合是否是合法的出棧順序。判斷一種出棧順序是否合法算法如下:
- 同時使用一個隊列q和一個堆棧s來解決該問題,其中,q存儲待判斷的出棧序列,而s用於模擬序列中每個元素的入棧和出棧過程。
- 這裏按照1-n的順序將每個元素壓入棧s:
- 每次壓入一個元素,檢查棧頂元素與隊列頭元素是否相同,若相同,s與q同時執行pop操作。
- 若最終棧s爲空,說明q裏存放的序列是合理的出棧順序,否則就是不合理的出棧序列。
程序(CPP):
#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
int n;
vector<int> a;
// 判斷出棧順序是否合法
bool check()
{
stack<int> s;
queue<int> q;
for(int i = 0; i < n; i++) q.push(a[i]);
for(int i = 1; i <= n; i++)
{
s.push(i);
while(!s.empty() && s.top()==q.front()) s.pop(), q.pop();
}
if(s.size()==0) return true;
else return false;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
a.push_back(i);
int cnt = 0;
do
{
if(check())
{
for(int i = 0; i < n; i++) cout << a[i];
cout << endl;
cnt++;
if(cnt==20) break;
}
}while(next_permutation(a.begin(), a.end()));
return 0;
}
四、火車進出棧問題
題目描述:
一列火車n節車廂,依次編號爲1,2,3,…,n。
每節車廂有兩種運動方式,進棧與出棧,問n節車廂出棧的可能排列方式有多少種。
輸入格式:
輸入一個整數n,代表火車的車廂數。
輸出格式:
輸出一個整數s表示n節車廂出棧的可能排列方式數量。
數據範圍:
輸入樣例:
3
輸出樣例:
5
思路:推導數學公式,是Catalan數,需要用到高精度。
程序(CPP):
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void multi(vector<long long> &a, int b)
{
long long t = 0;
for(int i = 0; i < a.size(); i++)
{
a[i] = a[i]*b+t;
t = a[i] / 1000000000;
a[i] %= 1000000000;
}
while(t)
{
a.push_back(t%1000000000);
t /= 1000000000;
}
}
void di(vector<long long> &a, int b)
{
long long t = 0;
for(int i = a.size()-1; i >= 0; i--)
{
a[i] += t*1000000000;
t = a[i]%b;
a[i] /= b;
}
while(a.size()>1 && a.back()==0) a.pop_back();
}
int main()
{
int n; cin >> n;
vector<long long> res;
res.push_back(1);
for(int i = 2*n, j=1; j<=n; i--, j++)
{
multi(res, i);
di(res, j);
}
di(res, n+1);
printf("%lld", res.back());
for(int i = res.size()-2; i >= 0; i--)
{
printf("%09lld", res[i]);
}
return 0;
}
五、直方圖中最大的矩形
題目描述:
直方圖是由在公共基線處對齊的一系列矩形組成的多邊形。矩形具有相等的寬度,但可以具有不同的高度。
例如,圖例左側顯示了由高度爲2,1,4,5,1,3,3的矩形組成的直方圖,矩形的寬度都爲1。
通常,直方圖用於表示離散分佈,例如,文本中字符的頻率。現在,請你計算在公共基線處對齊的直方圖中最大矩形的面積。圖例右圖顯示了所描繪直方圖的最大對齊矩形。
輸入格式
輸入包含幾個測試用例。每個測試用例佔據一行,用以描述一個直方圖,並以整數n開始,表示組成直方圖的矩形數目。然後跟隨n個整數。這些數字以從左到右的順序表示直方圖的各個矩形的高度。每個矩形的寬度爲1。同行數字用空格隔開。當輸入用例爲n=0時,結束輸入,且該用例不用考慮。
輸出格式
對於每一個測試用例,輸出一個整數,代表指定直方圖中最大矩形的區域面積。
每個數據佔一行。請注意,此矩形必須在公共基線處對齊。
數據範圍
,
輸入樣例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
輸出樣例:
8
4000
思路1:暴力枚舉,枚舉個子矩形,時間複雜度爲
#include <iostream>
using namespace std;
const int maxn = 1e5+10;
long long a[maxn];
int main()
{
int n;
while(cin >> n && n!=0)
{
for(int i = 1; i <= n; i++) cin >> a[i];
long long res = 0;
for(int i = 1; i <= n; i++)
{
long long h = a[i];
for(int j = i; j <= n; j++)
{
h = min(h, a[j]);
res = max(res, (j-i+1)*h);
}
}
cout << res << endl;
}
return 0;
}
思路2:單調棧
#include <iostream>
using namespace std;
const int maxn = 1e5+10;
long long a[maxn], s[maxn], w[maxn];
int main()
{
int n;
while(cin >> n && n!=0)
{
for(int i = 1; i <= n; i++) cin >> a[i];
long long res = 0;
int p = 0;
a[n+1] = 0;
for(int i = 1; i <= n+1; i++)
{
if(a[i]>s[p]) s[++p]=a[i], w[p]=1;
else
{
int width = 0;
while(s[p]>a[i])
{
width += w[p];
res = max(res, width*s[p]);
p--;
}
s[++p] = a[i], w[p] = width+1;
}
}
cout << res << endl;
}
return 0;
}