1. 程式人生 > >棧和佇列的常見面試題

棧和佇列的常見面試題

2、兩個棧實現一個佇列
【演算法思想】
1>設計類
成員變數:給兩個棧s1和s2來模擬實現一個佇列
成員函式:入隊Push()和出隊Pop()
2>給兩個指向棧物件s1、s2的指標input和output,分別用來入隊和出隊
3>按照先進先出的方式模擬入隊和出隊操作
Push:將input指向不空的棧,然後在input中入隊
Pop:將input所指棧中的前n-1(n為棧中元素個數)的資料先轉移到output所指的棧中,同時pop掉input中的前n-1個元素,最後pop掉input中的最後一個元素,即可實現先進先出。

【程式碼實現】

/*用兩個棧實現一個佇列*/
#pragma once #include <iostream> #include <stack> using namespace std; template <class T> class Queue { public: void Push(T data)//入隊 { stack<T>* input = NULL;//入隊指標 if (!s1.empty())//如果s1不為空,入隊指標就指向s1 input = &s1; else //如果s1為空,入隊指標就指向s2
input = &s2; input->push(data);//在入隊指標所指的棧中push資料 } void Pop()//出隊 { if (s1.empty() && s2.empty())//如果兩個棧都為空,說明佇列是空的,直接返回 { cout<<"queue empty!"<<endl; return; } stack<T>* input,*output;//定義入隊指標和出隊指標
if (s1.empty())//如果s1為空,就將入隊指標指向s2,出隊指標指向s1 { input = &s2; output = &s1; } else //如果s1不為空,就將入隊指標指向s1,出隊指標指向s2 { input = &s1; output = &s2; } size_t size = input->size();//用臨時變數size儲存佇列中的元素個數 for (size_t i = 0; i < size - 1; ++i)//將入隊指標所指棧的前(size-1)個元素轉入出隊棧 { output->push(input->top()); input->pop();//同時將轉移過的資料刪除 } input->pop();//將最先進入佇列的資料出隊 swap(input,output);//交換入隊和出隊指標 } protected: stack<T> s1; stack<T> s2; }; void TestQueue() { Queue<int> q; q.Push(1); q.Push(4); q.Push(3); q.Push(5); q.Pop(); q.Pop(); q.Pop(); q.Pop(); q.Pop(); }

3、實現一個棧,使其入棧push,出棧pop,獲取棧的最小元素min的時間複雜度均為O(1)
【基本思想】
考慮到棧的入棧push和出棧pop操作的時間複雜度本來就是O(1),所以說明該題的主要考點是要完成對獲取棧的最小元素min操作的時間複雜度的優化。
1>設計類
成員變數:給兩個棧s和min,s用來進行正常的入棧出棧操作,min棧用來儲存棧中的最小元素
成員函式:入隊Push()、出隊Pop()和獲取最小元素Min()
2>模擬實現題目要求的棧
Push:在棧s中push資料,將棧min中的最小元素大於等於當前入棧元素,就將當前元素也push進min棧中
Pop:更新min棧中的最小元素,如果要刪除的資料為最小資料,必須將min中的最小資料也刪除,否則只需刪除s棧的資料
Min:直接返回min棧的棧頂元素
【程式碼實現】

/*實現一個棧,要求實現Push(出棧)、Pop(入棧)、\
  Min(返回最小值的操作)的時間複雜度為O(1)*/

#pragma once
#include <iostream>
#include <stack>
using namespace std;

template <class T>
class  StackMin
{
public:
    void Push(T data)//入棧
    {
        s.push(data);//在棧s中入棧

        //如果min為空或者min中的最小值>=data,就將data同時入棧到min
        if (min.empty() || data <= min.top())
        {
            min.push(data);
        }
    }
    void Pop()//出棧
    {
        if (s.empty())//如果儲存資料的棧s為空,說明棧中沒資料,直接返回
            return;

        //如果要刪除的資料為最小資料,必須將min中的最小資料也刪除,否則只需刪除s棧的資料
        if (s.top() == min.top())
            min.pop();
        s.pop();//s.top()>min.top
    }
    T Min()//獲取棧中的最小值(時間複雜度:O(1))
    {
        return min.top();//min的棧頂元素即為最小值
    }
protected:
    stack<T> s;  //用來正常的出棧入棧
    stack<T> min;//用來儲存最小的資料
};

void TestStackMin()
{
    StackMin<int> sm;
    sm.Push(4);
    sm.Push(3);
    sm.Push(5);
    sm.Push(1);
    sm.Push(1);
    sm.Push(2);

    sm.Pop();
    sm.Pop();
}

4、判斷元素出棧入棧的合法性
對於一個棧,已知元素的入棧序列,判斷一個由棧中所有元素組成的排列是否是可能的出棧序列。
比如,進棧序列為1 2 3 4,則可能的出棧序列有4 3 2 1,1 4 3 2等。而1 4 2 3就不是。
【演算法思路】
給定一個棧s,按照入棧序列進行push資料,如果s的棧頂元素不等於出棧序列當前所指元素,就push入棧序列當前所指資料,同時讓入棧序列向後走;如果s的棧頂元素等於出棧序列當前所指元素,就pop掉s棧中的棧頂元素,同時讓出棧序列向後走。當入棧序列的所有元素都已入棧而出棧序列還沒走到尾時說明入棧出棧序列不合法,反之則是合法的。
【程式碼實現】

/*元素出棧、入棧順序的合法性。)*/
#pragma once
#include <iostream>
#include <stack>
#include <cassert>
using namespace std;

template <class T>
class Stack_Legal
{
public:
    bool IsLegal(T* in,T* out,size_t insz,size_t outsz)
    {
        assert(insz == outsz);//制止所給的入棧序列和出棧序列的長度不一樣

        size_t inIndex = 0;//入棧序列的下標
        size_t outIndex = 0;//出棧序列的下標

        while(outIndex != outsz)//出棧序列的下標沒走到尾,就說明還有元素沒有匹配出棧
        {
            //當棧為空或棧頂元素與出棧序列的當前元素不匹配
            if (s.empty() || s.top() != out[outIndex])
            {
                //如果所有元素都已入棧而出棧序列還沒有走到尾,說明序列不匹配
                if(inIndex == insz)
                    return false;
                //如果入棧序列還沒有走到尾,就將當前元素入棧
                s.push(in[inIndex++]);
            }
            //如果棧頂元素和出棧序列的當前元素相同,就將出棧序列的下標向後走,並pop棧頂元素
            else
            {
                ++outIndex;
                s.pop();
            }
        }

        return true;
    }

protected:
    stack<T> s;
};


void TestStackLegal()
{
    Stack_Legal<int> s;
    int in1[] = {1,2,3,4,5};
    int out1[] = {5,4,3,2,1};
    size_t insz1 = sizeof(in1)/sizeof(in1[0]);
    size_t outsz1 = sizeof(out1)/sizeof(out1[0]);
    cout<<s.IsLegal(in1,out1,insz1,outsz1)<<endl;

    int in2[] = {1,2,3,4,5};
    int out2[] = {1,2,3,4,5};
    size_t insz2 = sizeof(in2)/sizeof(in2[0]);
    size_t outsz2 = sizeof(out2)/sizeof(out2[0]);
    cout<<s.IsLegal(in2,out2,insz2,outsz2)<<endl;

    int in3[] = {1,2,3,4,5};
    int out3[] = {3,4,2,5,1};
    size_t insz3 = sizeof(in3)/sizeof(in3[0]);
    size_t outsz3 = sizeof(out3)/sizeof(out3[0]);
    cout<<s.IsLegal(in3,out3,insz3,outsz3)<<endl;

    int in4[] = {1,2,3,4,5};
    int out4[] = {5,2,4,3,1};
    size_t insz4 = sizeof(in4)/sizeof(in4[0]);
    size_t outsz4  = sizeof(out4)/sizeof(out4[0]);
    cout<<s.IsLegal(in4,out4,insz4,outsz4)<<endl;
}

5、用一個數組實現兩個棧
演算法一:
將陣列的兩頭分別作為兩個棧的棧頂指標,插入資料時向中間靠攏,指標相遇說明空間已滿,則需對陣列進行擴容

演算法二:
將陣列的中間兩個資料位置分別作為兩個棧的棧頂指標,插入資料時向兩邊走, 其中有一個指標走到邊界說明空間已滿,則需對陣列進行擴容
(缺點:有極端情況,一個棧滿了,另一個棧很空但仍需擴容,空間利用率低)

演算法三:
將陣列的奇偶位置分別作為兩個棧的棧頂指標,插入資料時各自向前走, 其中有一個指標走到右邊界說明空間已滿,則需對陣列進行擴容
(缺點:有極端情況,一個棧滿了,另一個棧很空但仍需擴容,空間利用率低)

基於使記憶體的空間利用率儘可能的高一些,此處只對演算法一進行實現。
【實現程式碼】

/*用一個數組實現兩個棧*/
#pragma once
#include <iostream>
#include <stack>
#include <cassert>
using namespace std;

enum Choose
{
    First,//對第一個棧進行操作
    SECOND,//對第二個棧進行操作
};

//演算法一:
/*將陣列的兩頭分別作為兩個棧的棧頂指標,插入資料時向中間靠攏,\
  指標相遇說明空間已滿,則需對陣列進行擴容*/
template <class T>
class ArrayStack
{
public:
    ArrayStack()
        :_n(5)//為了方便測試,預設陣列只給5個空間
        ,_a(new T[_n])
        ,_sTop1(0)
        ,_sTop2(_n-1)
    {}
    ~ArrayStack()
    {
        if (_a)
        {
            delete []_a;
            _a = NULL;
        }
    }

    void Push1(Choose choose,T data)//入棧(引數choose:選擇在哪一個棧中進行入棧)
    {
        assert(_a); //預防陣列為空

        if(_sTop1 > _sTop2)//當兩個下標已經交錯,說明陣列空間已滿,需要擴容
            _Dilatation();//擴容函式

        if (choose == First)//在第一個棧中push資料
            _a[_sTop1++] = data;
        else               //在第二個棧中push資料
            _a[_sTop2--] = data;
    }
    void Pop1(Choose choose)
    {
        if (choose == First)//pop第一個棧中的元素
        {
            if (_sTop1 == 0)//棧為空
            {
                cout<<"stack1 empty!"<<endl;
                return;
            }
            --_sTop1;//將棧頂下標向前移
        }
        else             //pop第二個棧中的元素
        {
            //size_t size2 = _a+n - _a +_sTop2;//儲存棧2 的元素個數
            if (_sTop2 == _n)//棧為空
            {
                cout<<"stack2 empty!"<<endl;
                return;
            }
            ++_sTop2;//將棧頂下標向後移
        }
    }
protected:
    void _Dilatation()//擴容函式
    {
        size_t n = _n;//儲存舊空間的元素個數
        _n *= 2;          //每次以二倍的速度擴容
        T* tmp = new T[_n];//開闢新空間

        //將棧1中舊空間的內容拷貝到新空間中
        for (size_t i = 0; i < _sTop1; ++i)
        {
            tmp[i] = _a[i];
        }

        size_t size2 = n - _sTop2;//儲存棧2的元素個數
        _sTop2 += n+1;//將棧2的棧頂指標相應的向後移動增加的位元組數

        //將棧2中舊空間的內容拷貝到新空間中
        for (size_t i = _n-1; --size2; --i)
        {
            tmp[i] = _a[--n];//注意迴圈條件和賦值方式
        }
        delete []_a;//釋放舊空間
        swap(_a,tmp);//指向新空間
    }
private:
    size_t _n;
    T* _a;
    size_t _sTop1;//棧1的棧頂下標
    size_t _sTop2;//棧2的棧頂下標
};

void TestArrayStack()
{
    ArrayStack<int> as;
    as.Push1(First,1);
    as.Push1(SECOND,5);
    as.Push1(First,2);
    as.Push1(SECOND,6);
    as.Push1(First,3);
    as.Push1(First,4);

    as.Pop1(First);
    as.Pop1(First);
    as.Pop1(First);
    as.Pop1(First);
    as.Pop1(First);
    as.Pop1(SECOND);
    as.Pop1(SECOND);
    as.Pop1(SECOND);
}