棧和隊列的常見面試題

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);
}
發佈了122 篇原創文章 · 獲贊 269 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章