您的位置:首页 > 理论基础 > 数据结构算法

第3章 栈和队列综合习题(leetcode+vjudge)

2017-10-06 10:17 405 查看
这里选择了一些栈和队列的练习,一部分来自leetcode,也有一部分通过vjudge

取自其他平台。没有选择综合性较强的题目,单纯使用栈、队列、线性表的基本操作和思想就可以解决了。

对于双端队列和优先队列,这里仅仅使用STL进行解决,说明一下使用他们进行解决的办法,不提及物理实现及相关问题。双端队列的物理实现可以使用带尾结点的链表;而优先队列需要使用二叉堆(binary heap)实现,这在第10章内排序再写总结。

用栈实现队列(Implement Stack using Queues)

需要使用栈完成一个队列,队列支持如下操作:

操作描述
push(x)
将x进入队列
pop()
将队首元素出列
peek()
返回队首元素
empty()
判断队列是否为空
要求只能使用基本的栈操作(
push
pop
empty
top
)。

栈是LIFO特性,而队列是FIFO特性。也就是说队列的出列,实际上要取栈底元素。因此这里可以使用两个栈来完成,需要取栈底元素,只需要将所有元素放入第二个栈,取其

栈顶就可以了。

参考解答:

class MyQueue {
public:
stack<int> st1, st2;
4000
/** Initialize your data structure here. */
MyQueue() {

}

/** Push element x to the back of queue. */
void push(int x) {
st1.push(x);
}

/** Removes the element from in front of queue and returns that element. */
int pop() {
while(!st1.empty()){
st2.push(st1.top());
st1.pop();
}
int result = st2.top();
st2.pop();
while(!st2.empty()){
st1.push(st2.top());
st2.pop();
}
return result;
}

/** Get the front element. */
int peek() {
while(!st1.empty()){
st2.push(st1.top());
st1.pop();
}
int result = st2.top();
while(!st2.empty()){
st1.push(st2.top());
st2.pop();
}
return result;
}

/** Returns whether the queue is empty. */
bool empty() {
return st1.empty();
}
};


用队列实现栈(Implement Stack using Queues )

需要使用队列完成一个栈,栈支持如下操作:

操作描述
push(x)
将x入栈
pop()
将栈顶元素出栈
top()
返回栈顶元素
empty()
判断栈是否为空
要求只能使用基本的队列操作(
push
pop
empty
front
)。

和上一道题较为类似,一样需要处理LIFO和FIFO的关系。也需要使用两个队列,取最后元素时,则已经取出的元素的个数总是比总数少1。将第二个队列作为临时空间,同时维护一个计数器就可以了。

class MyStack {
public:
queue<int> qu1;
queue<int> qu2;
int ptr;
/** Initialize your data structure here. */
MyStack() {

}

/** Push element x onto stack. */
void push(int x) {
qu1.push(x);
}

/** Removes the element on top of the stack and returns that element. */
int pop() {
int counter = 0;
while(!qu1.empty()){
qu2.push(qu1.front());
qu1.pop();
counter++;
}
counter--;        //将计数器自减,以便于取出队尾元素
while(counter > 0){
qu1.push(qu2.front());
qu2.pop();
counter--;
}
int result = qu2.front();
qu2.pop();
return result;
}

/** Get the top element. */
int top() {
int counter = 0;
while(!qu1.empty()){
qu2.push(qu1.front());
qu1.pop();
counter++;
}
counter--;
while(counter > 0){
qu1.push(qu2.front());
qu2.pop();
counter--;
}
int result = qu2.front();
qu2.pop();
qu1.push(result);
return result;
}

/** Returns whether the stack is empty. */
bool empty() {
return qu1.empty();
}
};


最小栈(Min Stack)

要求设计一个栈,除了支持最基本的栈操作,还需要支持在O(1)时间内完成的取最小元素的操作。

这是《数据结构与算法分析C语言描述》的一道练习题。在常数时间内完成取最小值操作,可以考虑维护一个
min
变量,指向最小元素的下标。在入栈和出栈时维护这个特性即可。下面的解法,能够保证插入O(1),删除O(n)。

class MinStack {
public:
/** initialize your data structure here. */
vector<int> _data;
int min;
MinStack() {
min = -1;
}

void push(int x) {
if(_data.empty()){
min = 0;
}
_data.push_back(x);
min = _data.back() < _data[min] ? _data.size() - 1 : min;
}

void pop() {
if(_data.empty()){
return;
}
_data.pop_back();
min = 0;
for(int i = 1; i < _data.size(); ++i){
if(_data[i] < _data[min]){
min = i;
}
}
}

int top() {
return _data.back();
}

int getMin() {
return _data[min];
}
};


字符串解码(Decode String)

按照如下规则解码字符串:

字符串有着如下描述:
k[encoded_string]
,表示将
encoded_string
重复k次。特别的,当
k = 1
时,
k
和中括号
[]
省略不写。可以嵌套。字符串
encoded_string
不包含小写字母以外的其他字符。整个字符串不包括空格等其他字符。

例如

s = "3[a]2[bc]", return "aaabcbc".
s = "3[a2[c]]", return "accaccacc".
s = "2[abc]3[cd]ef", return "abcabccdcdcdef".


这道题是使用栈的一道很好的题目;这道题目也能展现递归程序非递归化的一些内容。

与双栈法直接求表达式值类似,这里也使用了2个栈,一个用来存放k的值,一个用来存放当前层解码后的字符串。扫描到数字时,需要将其转化成正整数k,扫描到左括号,将深度增加一层,当前层处理后的字符串入栈,并且将k入栈(k指的是下一层需要的重复次数)。扫描到右括号,将当前层的字符串重复k次(取自栈顶),然后与前一层的字符串进行拼接。最后只需要返回第一层的字符就可以了。

如果从递归的角度来看,体现了保存状态-自我调用-恢复状态的过程。只不过在递归方法,依赖于一定的硬件体系结构(栈帧模型),编译器为我们自动完成了这些操作。

class Solution {
public:
string decodeString(string s) {
stack<int> st;
stack<string> st_s;
int num = 0;
int counter;
string dstr = "", tmp;
for(auto it = s.begin(); it != s.end(); ++it){
if(*it >= '0' && *it <= '9'){
num = num * 10 + (*it - '0');
}else if(*it == '['){
st_s.push(dstr);
dstr = "";
st.push(num);
num = 0;
}else if(*it == ']'){
counter = st.top();
st.pop();
tmp = st_s.top();
while(counter--){
tmp += dstr;
}
st_s.pop();
dstr = tmp;
}else{
dstr.push_back(*it);
}
}
return dstr;
}
};


打印机队列(Printer Queue,POJ 3125,NWERC 2006)

每个任务都有不同的优先级,数字越大越紧急。打印机需要这样处理这些任务:

第一个任务如果不是当前所有任务中最紧急的,则放到最后;
否则,处理并移除该任务。


我们关心指定位置的任务需要多久才能出结果,你需要回答这个问题。假设处理每个任务需要一分钟,其他操作不占用时间。

比如

Input:
4 2
1 2 3 4
Output:
2


又比如

Input:
6 0
1 1 9 1 1 1
Output:
5


这是一到使用双端队列、优先队列解决问题的题目,主要是队列的模拟用法。使用优先队列存储优先级,而双端队列模拟整个过程。

#include<cstdio>
#include<algorithm>
#include<deque>
#include<queue>
using namespace std;
struct person
{
int level;
bool me;
person(int lv,int m):level(lv),me(m){}
};
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
priority_queue<int> pq;
deque<person> dq;
int n,mypos,i;
scanf("%d%d",&n,&mypos);
for(i=0;i<n;i++)
{
int tmp;
scanf("%d",&tmp);
if(i==mypos)
dq.push_back(person(tmp,true));
else
dq.push_back(person(tmp,false));
pq.push(tmp);
}
int cnt=1;
while(!pq.empty())
{
if(dq.front().level!=pq.top())
{
dq.push_back(dq.front());
}
else
{
if(dq.front().me) break;
else
{
pq.pop();
cnt++;
}
}
dq.pop_front();
}
printf("%d\n",cnt);
}
return 0;
}


看病要排队(HDU 1873, 2008浙江大学研究生复试全真模拟 )

0068所去的医院有三个医生同时看病。而看病的人病情有轻重,所以不能根据简单的先来先服务的原则。所以医院对每种病情规定了10种不同的优先级。级别为10的优先权最高,级别为1的优先权最低。医生在看病时,则会在他的队伍里面选择一个优先权最高的人进行诊治。如果遇到两个优先权一样的病人的话,则选择最早来排队的病人。

输入数据包含多组测试,请处理到文件结束。

每组数据第一行有一个正整数N (0<N<2000)表示发生事件的数目。

接下来有N行分别表示发生的事件。

一共有两种事件:

1:
IN A B
,表示有一个拥有优先级B的病人要求医生A诊治。(0<A<4,0<B<11)

2:
OUT A
,表示医生A进行了一次诊治,诊治完毕后,病人出院。(0<A<4)

对于每个
OUT A
事件,请在一行里面输出被诊治人的编号ID。如果该事件时无病人需要诊治,则输出
EMPTY


诊治人的编号ID的定义为:在一组测试中,
IN A B
事件发生第K次时,进来的病人ID即为K。从1开始编号。

例如

[Input]
7
IN 1 1
IN 1 2
OUT 1
OUT 2
IN 2 1
OUT 2
OUT 1
2
IN 1 1
OUT 1
[Output]
2
EMPTY
3
1
1


这道题同样是优先队列的使用。优先队列的使用,关键之一在于自定义比较函数。这里可以很容易根据题目要求构造出比较函数(STL的仿函数),然后直接使priority_queue就可以了。

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
struct patient
{
int priority;
int time;
patient(int p, int t)
{
priority = p;
time = t;
}
};
struct mycmp
{
bool operator()(patient a, patient b)
{
if (a.priority == b.priority)
return a.time > b.time;
else
return a.priority < b.priority;
}
};
int main()
{
int T;
while (scanf("%d", &T) != EOF)
{
priority_queue<patient, vector<patient>, mycmp> doctors[4];
int id = 0;
while (T--)
{
char ops[10];
scanf("%s", ops);
if (!strcmp(ops, "IN"))
{
int d, p;
scanf("%d %d", &d, &p);
doctors[d].push(patient(p, ++id));
}
else
{
int d;
scanf("%d", &d);
if (doctors[d].empty())
printf("EMPTY\n");
else
{
printf("%d\n", doctors[d].top().time);
doctors[d].pop();
}
}
}
}
return 0;
}


网页浏览器(Web Navigation,POJ 1028)

模拟实现浏览器的前进和后退操作。

VISIT 表示浏览某地址
FORWARD 表示前进
BACK 表示后退


详细描述见POJ。

题目描述提示使用了双栈实现,实际上这是一个类似共享栈模型的问题。使用顺序存储结构很容易实现。VISIT操作,将整个顺序表扩容,当前页面指针也同时向后移动,同时将整个栈顶移动;BACK操作将当前指针前移就可以了。

#include<iostream>
#include<string>
#include<vector>
#include<stack>
using namespace std;
int main()
{
int cptr = 0, maxsz;
string list[200];
string istr;
list[0] = "http://www.acm.org/";
while (cin >> istr)
{
switch (istr[0])
{
case 'V':
cin >> istr;
list[++cptr] = istr;
maxsz = cptr;
cout << istr << endl;
break;
case 'B':
if (cptr > 0)
{
cout << list[--cptr] << endl;
}
else
{
cptr = 0;
cout << "Ignored" << endl;
}
break;
case 'F':
if (cptr == maxsz)
{
cout << "Ignored" << endl;
}
else
{
cout << list[++cptr] << endl;
}
break;
case 'Q':
return 0;
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息