1. 程式人生 > >第3章 棧和佇列綜合習題(leetcode+vjudge)

第3章 棧和佇列綜合習題(leetcode+vjudge)

這裡選擇了一些棧和佇列的練習,一部分來自leetcode,也有一部分通過vjudge
取自其他平臺。沒有選擇綜合性較強的題目,單純使用棧、佇列、線性表的基本操作和思想就可以解決了。
對於雙端佇列和優先佇列,這裡僅僅使用STL進行解決,說明一下使用他們進行解決的辦法,不提及物理實現及相關問題。雙端佇列的物理實現可以使用帶尾結點的連結串列;而優先佇列需要使用二叉堆(binary heap)實現,這在第10章內排序再寫總結。

用棧實現佇列(Implement Stack using Queues)

需要使用棧完成一個佇列,佇列支援如下操作:

操作 描述
push(x)
將x進入佇列
pop() 將隊首元素出列
peek() 返回隊首元素
empty() 判斷佇列是否為空

要求只能使用基本的棧操作(pushpopemptytop)。

棧是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() 判斷棧是否為空

要求只能使用基本的佇列操作(pushpopemptyfront)。

和上一道題較為類似,一樣需要處理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<40<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;
}