一、设计一个最小栈
题目要求:设计一个支持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;
}
来源:CSDN
作者:程勇uestc
链接:https://blog.csdn.net/qq_40438165/article/details/103935595