第3章 棧和佇列綜合習題(leetcode+vjudge)
這裡選擇了一些棧和佇列的練習,一部分來自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;
/** 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)
要求設計一個棧,除了支援最基本的棧操作,還需要支援在
這是《資料結構與演算法分析C語言描述》的一道練習題。在常數時間內完成取最小值操作,可以考慮維護一個min
變數,指向最小元素的下標。在入棧和出棧時維護這個特性即可。下面的解法,能夠保證插入
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的優先權最低。醫生在看病時,則會在他的隊伍裡面選擇一個優先權最高的人進行診治。如果遇到兩個優先權一樣的病人的話,則選擇最早來排隊的病人。
輸入資料包含多組測試,請處理到檔案結束。
每組資料第一行有一個正整數
接下來有
一共有兩種事件:
1:IN A B
,表示有一個擁有優先順序B的病人要求醫生A診治。(
2:OUT A
,表示醫生A進行了一次診治,診治完畢後,病人出院。(
對於每個OUT A
事件,請在一行裡面輸出被診治人的編號ID。如果該事件時無病人需要診治,則輸出EMPTY
。
診治人的編號ID的定義為:在一組測試中,IN A B
事件發生第
例如
[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;
}