遞歸->棧->隊列面試題

本文所有程序均已測試通過,測試結果圖就不一個一個再截圖了;讀者可以自己copy驗證一下;後期我會把思路圖補出來

1.行走機器人問題:貨架N個,機器人初始位置在pos,經過minutes分鐘後到達T有多少種方案

這裏寫圖片描述

//行走機器人問題:貨架N個,機器人初始位置在pos,經過minutes分鐘後到達T有多少種方案
int Walkrobb(int n, int pos, int minutes, int t)
{
    while (n >= 0 && pos >= 0 && minutes >= 0 && t >= 0)
    {
        int arr[100][100];
        memset(arr, 0, sizeof(arr));
        arr[0][pos] = 1;
        for (int i = 1; i <= minutes; i++)//第i分鐘
        {
            for (int j = 1; j <= n; j++)//第j位置
            {
                arr[i][j] = arr[i-1][j + 1] + arr[i-1][j - 1];
                //第i分鐘走到第j位置的方案等於移動兩邊方案之和
            }
        }
        return arr[minutes][t];
        //返回第minutes分鐘走到t位置的方案
    }
    return 0;
}

int main()
{
    int num = Walkrobb(3, 2, 4, 2);
    cout << num << endl;
    system("pause");
}

2.迷宮

回溯法
基本思想:對一個包括有很多個結點,每個結點有若干個搜索分支的問題,把原問題分解爲多若干個子問題求解的算法;當搜索到某個結點發現無法再繼續搜索下去時,就讓搜索過程回溯(回退)到該節點的前一個結點,繼續搜索該節點外的其他尚未搜索的分支;如果發現該結點無法再搜索下去,就讓搜索過程回溯到這個結點的前一結點繼續這樣的搜索過程;這樣的搜索過程一致進行到搜索到問題的解或者搜索完了全部可搜索分子沒有解存在爲止。

#define ROW 3
#define COL 6
struct seat
{
public:
    seat(int x,int y)
    :_x(x)
    , _y(y)
    {}
    int _x;
    int _y;
};

class maze
{
public:
    maze(int arr[ROW][COL])
    {
        for(int i = 0; i < ROW; i++)
        {
            for(int j = 0; j < COL; j++)
            {
                _map[i][j] = arr[i][j];
            }
        }
    }

    bool IsPass(const seat &s)
    {
        if (_map[s._x][s._y] == 1)
            return true;
        return false;
    }

    bool PassMaze(const seat &s)
    {
        if (s._x < 0 || s._y >= COL || s._x >= ROW || s._y < 0)
        {
            return true;
        }
        if (IsPass(s))
        {
            _map[s._x][s._y] = 2;

            seat up(s._x - 1, s._y);
            seat down(s._x + 1, s._y);
            seat left(s._x, s._y - 1);
            seat right(s._x, s._y + 1);

            if (PassMaze(up))
            {
                return true;
            }
            else if (PassMaze(left))
            {
                return true;
            }
            else if (PassMaze(right))
            {
                return true;
            }
            else if (PassMaze(down))
            {
                return true;
            }
            else
            {
                _map[s._x][s._y] = 3;
            }

        }
        return false;
    }

    void PrintMap()
    {
        for (int i = 0; i < ROW; i++)
        {
            for (int j = 0; j < COL; j++)
            {
                cout << _map[i][j]<<" ";
            }
            cout << endl;
        }
        cout << endl;
    }
private:
    int _map[ROW][COL];
};

int main()
{
    int arr[ROW][COL] =
    { 
        { 0, 1, 0, 0, 0, 0 }, 
        { 0, 1, 1, 0, 0, 0 }, 
        { 0, 0, 1, 0, 0, 0 }
    };
    maze m(arr);
    m.PrintMap();
    m.PassMaze(seat(2, 2));
    m.PrintMap();
    system("pause");
}

3.括號匹配問題(用棧實現)

解題思路:
(1)只要遇到左括號進壓棧;
(2)遇到右括號並且棧不爲空,則檢測是否與當前棧頂的左括號匹配;如果匹配,則棧頂左括號出棧;如果不匹配,則括號不對應;如果棧空了,則右括號多餘;
(3)循環以上步驟
(4)最後如果棧空了,則匹配正確;否則,左括號多餘

#include <stack>

class IsBarcketMate
{
public:
    IsBarcketMate(char *arr ="")
        :_arr(arr)
    {
    }

    bool IsBarcket()
    {
        stack<char>h;
        size_t i =0;
        while (i < strlen(_arr) && _arr && *_arr!='\0')
        {
            //出現左括號則進棧
            if (*_arr == '(' || *_arr == '{' || *_arr == '[')
            {
                h.push(*_arr);
                _arr++;
            }
            //出現右括弧
            else if (*_arr == ')' || *_arr == '}' || *_arr == ']')
            {
                //首先檢測棧是否爲空並且和棧頂數據比較,若匹配則棧頂出棧
                if (!h.empty() && ((h.top() == '(') && (*_arr == ')') || (h.top() == '{') && (*_arr == '}') || (h.top() == '[') && (*_arr == ']')))
                {
                    h.pop();
                    _arr++;
                }
                //若棧空則表明此右括號多餘,表達式不匹配
                else if (h.empty())
                {
                    cout <<"右括號多餘"<< endl;
                    return false;
                }
                //否則括號不對應
                else
                {
                    cout << "括號不匹配" << endl;
                    return false;
                }
            }
            //處理除了括號以外的字符
            else
            {
                _arr++;
            }
        }
        //最後若棧空,則匹配正確,否則棧裏還有左括號
        if (h.empty())
        {
            cout << "括號匹配" << endl;
            return true;
        }
        else
        {
            cout << "左括號多餘" << endl;
            return false;

        }
    }
private:
    char *_arr;
};

void test()
{
    char arr1[] = "((()))";
    char arr2[] = "((())))";
    char arr3[] = "(((()))";
    char arr4[] = "(1244ki)";
    char arr5[] = "{([])}";

    IsBarcketMate s1(arr1);
    IsBarcketMate s2(arr2);
    IsBarcketMate s3(arr3);
    IsBarcketMate s4(arr4);
    IsBarcketMate s5(arr5);
    s1.IsBarcket();
    s2.IsBarcket();
    s3.IsBarcket();
    s4.IsBarcket();
    s5.IsBarcket();

}

int main()
{
    test();
    system("pause");
}

4.假設有一個能裝入總體積爲T的揹包和n件體積分別爲w1,w2,…wn的物品,能否從n件物品中挑選若干件恰好裝滿揹包,即使w1+w2+…+wm=T,要求找出所有滿足上述條件的解。(用棧實現)

解題思路:
(1)首先,將物品排成一列,然後,順序選取物品裝入揹包;
(2)若已選取第i件物品後未滿,則繼續選取第i + 1件;
若該件物品“太大”不能裝入,則棄之,繼續選取下一件;
若揹包滿了,則輸出,並且丟棄最後進來的物品;
(3)如果在剩餘的物品中找不到合適的物品以填滿揹包,則說明“剛剛”裝入的物品“不合適”,應將它取出“棄之一邊”,繼續再從“它之後”的物品中重新選取;
(4)重複以上步驟,直到沒有物品可以放;
(5)換棧底,也就是拋棄最底層的元素,讓下一個開始匹配,
(6)重複以上步驟,直到沒有物品作棧底;

#include <stack>
class  Backpack
{
public:
    Backpack(int *arr)
    {
        _arr = arr;
    }

    void IsBackpack(int t,int n)
    {
        stack<int>h;
        int count = 0;//計算總和
        int i = 0;//當前壓棧的件數
        bool key = false;//判斷是否有解
        int border = n;
        //如果沒有其他物品可以作爲棧底,停止循環
        //外層循環用來挪動棧底
        while (i < n)//循環條件i從0開始,對應的是n的下標
        {

            //判斷棧底是否爲t;如果是t,則直接輸出;跳到下一個元素作爲棧底,並且總件數-1;
            if (*_arr == t)
            {
                cout << *_arr << endl;
                --n;
                _arr++;
                key = true;      //到這就說明有解,把key設置成true
            }

            //newarr表示當前棧底;
            int *Newarr = _arr;

            int m = n;
            int j = 0;
            //如果沒有其他物品可以選入揹包
            //內層循環是在確定了棧底的基礎上,挪動其他件數
            while(i < m)
            {

                h.push(Newarr[j]);//把當前元素壓進去(第一次循環壓進去的棧底)
                i++;            //件數+1
                count += Newarr[j];//count計數

                //如果揹包剛好滿了
                if (t - count == 0)
                {
                    //則輸出當前解
                    stack<int> s(h);
                    while (!s.empty())
                    {
                        cout << s.top() << " ";
                        s.pop();
                    }
                    cout << endl;


                    count -= h.top();//count減去棧頂元素值
                    h.pop();         //釋放棧頂元素
                    j++;        //跳到下一個元素
                    key = true;      //到這就說明有解,把key設置成true
                }

                //如果揹包沒滿,則跳到下一個元素;
                else if (t - count > 0)
                {

                    j++;
                }

                //揹包溢出,釋放棧頂元素;count減去當前值
                else
                {

                    count -= h.top();
                    h.pop();
                    j++;    
                }


//如果在剩餘的物品中找不到合適的物品以填滿揹包,則說明“剛剛”裝入的物品“不合適”,
//應將它取出“棄之一邊”,繼續再從“它之後”的物品中重新選取。

                if (i == m )//當物品放完了,還是沒有合適的填滿揹包(最後一個物品被認爲太大,已經在前面釋放)
                                             //說明上一個物品不合適,也就是當前的棧頂元素;
                {
                    count -= h.top();//count減去“不合適”物品體積

                    //找到“不合適”物品的座標
                    for (int k = 0; k < sizeof(Newarr); k++)
                    {
                        if (Newarr[k] == h.top())
                        {
                            j = k;
                            break;
                        }
                    }
                    h.pop();//丟棄它
                    --m;    //總件數減1
                    i = h.size();//設置當前件數爲棧元素個數
                    j++;//跳過它,重新取後面的物品

                }
            }


            //釋放棧空間,準備換棧底
            while (!h.empty())
            {
                h.pop();
            }
            --n;//總件數減1
            _arr++;//跳到下一個元素
            i = 0;//件數歸0
            count = 0;//總和歸0
        }


        //無解
        if (!key)
        {
            cout << "無解"<< endl;
        }

    }


private:
    int *_arr;
};


void test()
{
    int arr[6] = {1,8,4,3,5,2};
    Backpack b(arr);
    b.IsBackpack(10,6);

}

int main()
{
    test();
    system("pause");
}

5.兩個棧實現一個隊列

解題思路:

實現一:

s1是入棧的,s2是出棧的;
入隊列,直接壓到s1是就行了;
出隊列,先把s1中的元素全部出棧壓入到s2中,彈出s2中的棧頂元素;再把s2的所有元素全部壓回s1中。

實現二:

上述思路,可行性毋庸置疑。但有一個細節是可以優化一下的。即:在出隊時,將s1的元素逐個“倒入”s2時,原在s1棧底的元素,不用“倒入”s2(即只“倒”s1.size( )- 1個),可直接彈出作爲出隊元素返回。這樣可以減少一次壓棧的操作。

實現三:

入隊時,先判斷s1是否爲空;
如不爲空,說明所有元素都在s1,此時將入隊元素直接壓入s1;
如爲空,要將s2的元素逐個“倒回”s1,再壓入入隊元素。

出隊時,先判斷s2是否爲空;
如不爲空,直接彈出s2的棧頂元素並出隊;
如爲空,將s1的元素逐個“倒入”s2,把最後一個元素彈出並出隊。

比較方案一和方案三:
相對於方案一,方案三的s2好像比較“懶”,每次出隊後,並不將元素“倒回”s1,如果趕上下次還是出隊操作,效率會高一些,但下次如果是入隊操作,效率不如第一種方法。入隊、出隊操作隨機分佈時,上述兩種方法總體上時間複雜度和空間複雜度應該相差無幾(無非多個少個判斷)。

實現四
s1是入棧的,s2是出棧的。
入隊列:直接壓入s1即可
出隊列:如果s2不爲空,把s2中的棧頂元素直接彈出;否則,把s1的所有元素全部彈出壓入s2中,再彈出s2的棧頂元素比較
這個思路,避免了反覆“倒”棧,僅在需要時才“倒”一次。

//實現方案四
#include <stack>
template <class T>
class MyQueue
{
public:
    void push(T elem) 
    {
        s1.push(elem);
        back_elem = elem;
    }

    void pop() 
    {
        if (!s2.empty()) 
        {
            s2.pop();
        }
        else if (!s1.empty()) 
        {
            while (!s1.empty()) 
            {
                s2.push(s1.top());
                s1.pop();
            }
            s2.pop();
        }
        else 
        {
            cout << "error pop(), empty queue!" << endl;
        }
    }

    T front()
    {
        if (!s2.empty()) 
        {
            return s2.top();
        }
        else if (!s1.empty()) 
        {
            while (!s1.empty()) 
            {
                s2.push(s1.top());
                s1.pop();
            }
            return s2.top();
        }
        else 
        {
            cout << "error front(), empty queue!" << endl;
        }
    }

    T back(){
        if (!empty())
            return back_elem;
        else {
            cout << "error back(), empty queue!" << endl;
            return 0;
        }
    }

    int size() const 
    {
        return s1.size() + s2.size();
    }

    bool empty() const 
    {
        return s1.empty() && s2.empty();
    }
private:
    stack<T> s1;
    stack<T> s2;
    T back_elem;

};

6.兩個隊列實現一個棧

解題思路:

實現一:

q1是專職進出棧的,q2只是箇中轉站;
入棧:直接入隊列q1即可;
出棧:把q1的除最後一個元素外全部轉移到隊q2中, 然後把剛纔剩下q1中的那個元素出隊列。之後把q2中的全部元素轉移回q1中。

實現二:

其中一個是專職進出棧的,另一個是中轉站,可交替入棧:直接入隊列q1即可;

出棧:把q1的除最後一個元素外全部轉移到隊q2中, 然後把剛纔剩下q1中的那個元素出隊列。之後不需要把q2中的全部元素轉移回q1中
此時,q2作爲專職進出棧的,q1作爲中轉站。避免了重複“倒”數據

//實現方案二
#include <queue>
template <class T>
class Stack
{
public:
    Stack() 
    {
        q1_used = true;
        q2_used = false;
    }

    void push(T elem) 
    {
        if (q1_used == true) 
        {
            q1.push(elem);
        }
        if (q2_used == true)
        {
            q2.push(elem);
        }
    }

    void pop() 
    {
        if (!q1.empty() && q1_used == true) 
        {
            while (q1.size() != 1) 
            {
                q2.push(q1.front());
                q1.pop();
            }
            q1.pop();
            q2_used = true;
            q1_used = false;
            return;
        }
        if (!q2.empty() && q2_used == true) 
        {
            while (q2.size() != 1) 
            {
                q1.push(q2.front());
                q2.pop();
            }
            q2.pop();
            q2_used = false;
            q1_used = true;
            return;
        }
        cout << "error pop()" << endl;
    }


    T top() const {
        if (!q1.empty() && q1_used == true) 
        {
            return q1.back();
        }
        else if (!q2.empty() && q2_used == true) 
        {
            return q2.back();
        }
        cout << "error top()" << endl;
        return 0;
    }

    bool empty() const 
    {
        return q1.empty() && q1_used == true || q2.empty() && q2_used == true;
    }

    int size() const 
    {
        if (!q1.empty() && q1_used == true) 
        {
            return q1.size();
        }
        if (!q2.empty() && q2_used == true) 
        {
            return q2.size();
        }
        return 0;
    }
private:
    queue<T> q1;
    queue<T> q2;
    bool q1_used, q2_used;
};

7.實現一個棧,要求Push(入棧),Pop(出棧),Min(返回最小值的操作)的時間複雜度爲O(1)

解題思路:

方法一 :

使用一個棧;元素x入棧時,執行一次push(x),再push(min),min表示當前棧頂到棧底元素最小值;元素出棧時,執行兩次pop();

這種方案有缺陷:
如果我們要入棧的元素序列是遞增的(1,2,3……1000),那麼每一次入棧都要push(1),操作冗餘。

方法二:

使用兩個棧s1和s2,s2做爲輔助棧(每次壓入s2的都是s1的最小值);

元素x入棧時,將x和s2棧頂元素做比較,
如果x小於等於s2.top(),將x分別push到s1和s2,否則x只push到s1;

元素出棧時,將s1棧頂元素和s2棧頂元素做比較,
如果s1.top()等於s2.top(),s1和s2都執行pop操作,否則只執行
s1.pop();

 //實現方案二
#include<stack>
typedef int T;
class StackWithMin
{
public:
    StackWithMin()
    {}
    ~StackWithMin()
    {}
    void Push(T x)
    {
        s1.push(x);
        if (s2.empty() || x <= s2.top())
        {
            s2.push(x);
        }
    }
    void Pop()
    {
        if (s1.top() == s2.top())
        {
            s2.pop();
        }
        s1.pop();
    }
    void Min()
    {
        if (!s2.empty())
        {
            cout << s2.top() << endl;
        }

    }
    void Print()
    {
        while (!s1.empty())
        {
            cout << s1.top() << "->";
            s1.pop();
        }
        cout << endl;
        while (!s2.empty())
        {
            cout << s2.top() << "->";
            s2.pop();
        }
        cout << endl;
    }
private:
    stack<int> s1;
    stack<int> s2;
};

void Test()
{
    StackWithMin  s;
    s.Push(4);
    s.Push(3);
    s.Push(5);
    s.Push(2);
    s.Push(1);
    s.Pop();
    s.Min();
    s.Print();

}

int main()
{
    Test();
    system("pause");
}

8.用一個數組實現兩個棧(貢獻棧)

解題思路:

方案一:偶奇數壓棧

將數組的下標爲0的位置當做第一個棧的棧底,下標爲1的位置當做第二個棧的棧底,將數組的偶數位置看做第一個棧的存儲空間,奇數位置看做第二個棧的存儲空間。

方案二:從中間分別向兩邊壓棧

將數組的中間位置看做兩個棧的棧底,壓棧時棧頂指針分別向兩邊移動,當任何一邊到達數組的起始位置或是數組尾部,則開始擴容。

方案三:從兩邊向中間壓棧

將數組的起始位置看作是第一個棧的棧底,將數組的尾部看作第二個棧的棧底,壓棧時,棧頂指針分別向中間移動,直到兩棧頂指針相遇,則擴容。

方案比較:
(1)擴容條件

方案一和方案二明顯在擴容時考慮的情況比較複雜,條件更多一些;方案三在擴容的時候只需要判斷兩個棧頂相遇了,就擴容。

(2)空間使用率:

方案一和方案二隻要其中一個棧壓入比較多的數據,而另外一個棧壓入的數據很少時,就存在非常大的空間浪費;

就比如說方案一偶奇數壓棧:我要是隻壓偶數棧,不壓奇數棧,浪費的空間太多了。。。。

所以比較而言,方案三就可以很好的避免這一情況,空間利用率比較高,
而且這種方案在一個棧pop的空間另一個棧也可以使用,可以在一些情況下減少開闢空間的次數(畢竟在c++中動態開闢空間還是很耗時的)

//實現方案三
template<class T>
class TwoStack
{
public:
    TwoStack()
        :_a(NULL)
        , _top1(0)
        , _top2(0)
        , _capacity(0)
    {
        _CheckCapacity();
    }
    ~TwoStack()
    {
        if (_a)
            delete[] _a;
    }
    void Push1(const T& d)
    {
        _CheckCapacity();
        _a[_top1++] = d;
    }
    void Push2(const T& d)
    {
        _CheckCapacity();
        _a[_top2--] = d;
    }
    void Pop1()
    {
        if (_top1 > 0)
            --_top1;
    }
    void Pop2()
    {
        if (_top2 < _capacity - 1)
            _top2++;
    }
    size_t Size1()
    {
        return _top1;
    }
    size_t Size2()
    {
        return _capacity - 1 - _top2;
    }
    bool Empty1()
    {
        return _top1 == 0;
    }
    bool Empty2()
    {
        return _top2 == _capacity - 1;
    }
    T& Top1()
    {
        if (_top1>0)
        {
            return _a[_top1 - 1];
        }
    }
    T& Top2()
    {
        if (_top2 < _capacity - 1)
            return _a[_top2 + 1];
    }

private:
    void _CheckCapacity()
    {
        if (_a == NULL)
        {
            _capacity += 3;
            _a = new T[_capacity];
            _top2 = _capacity - 1;
            return;
        }
        if (_top1 == _top2)
        {
            size_t OldCapacity = _capacity;
            _capacity = _capacity * 2;
            T* tmp = new T[_capacity];
            for (size_t i = 0; i < _top1; i++)
            {
                tmp[i] = _a[i];
            }
            for (size_t j = OldCapacity - 1, i = _capacity - 1; j>_top2; j--, i--)
            {
                tmp[i] = _a[j];
            }
            delete[] _a;
            _a = tmp;
            _top2 += _capacity / 2;
        }
    }

private:
    T* _a;
    size_t _top1;
    size_t _top2;
    size_t _capacity;
};

void Test()
{
    TwoStack<int> s;
    s.Push1(1);
    s.Push1(2);
    s.Push1(3);
    s.Push1(4);
    s.Push1(5);
    s.Push2(1);
    s.Push2(2);
    s.Push2(3);
    s.Push2(4);
    s.Push2(5);
    cout << "s1:" << s.Size1() << endl;;
    cout << "s2:" << s.Size2() << endl;
    while (!s.Empty1())
    {
        cout << s.Top1() << endl;
        s.Pop1();
    }
    while (!s.Empty2())
    {
        cout << s.Top2() << endl;
        s.Pop2();
    }

}

int main()
{
    Test();
    system("pause");
}

9.元素出棧、入棧順序的合法性

如入棧的序列(1,2,3,4,5),出棧序列爲(4,5,3,2,1)

解題思路:

大體是這樣的:
定義兩個順序表存放這兩個序列;
把第一個序列中的數字依次壓入棧中,邊壓邊按照第二個序列的順序依次從棧中彈出數字。

具體思路:

大前提是:兩個序列的元素個數是一樣的,否則,不匹配;

入棧順序表中的元素開始壓棧;
遍歷出棧順序表中的元素,有兩種情況 :
(1)如果當前元素是棧頂的元素,則pop出來;
(2)如果不是棧頂元素,則根據入棧順序將沒入棧的元素push進棧,直到將該元素push棧中,然後將該元素pop出來;
(3)重複以上步驟
(4)最後輔助棧爲空棧,那麼該序列就是合格的;否則,不合格;


#include <stack>
#include <vector>
bool IsPopOrder(vector<int>pushV, vector<int>popV)
{
    if (pushV.size() != popV.size())
        return false;
    int len = pushV.size();
    int indexPopV = 0;
    stack<int> s;
    for (int i = 0; i < len; ++i)
    {
        s.push(pushV[i]);
        while (s.size()!=0 && s.top() == popV[indexPopV])
        {
            s.pop();
            indexPopV++;
        }
    }

    if (s.empty())
    {
        return true;
    }
    return false;
}


void test()
{
    vector<int>pushV = { 1, 2, 3, 4, 5 };       //入棧序列
    vector<int>popV = { 4, 5, 3, 2, 1 };      //出棧序列
    bool ret = IsPopOrder(pushV, popV);
    if (ret)
        cout << "出棧順序合法" << endl;
    else
        cout << "出棧順序不合法" << endl;
}

int main()
{

    test();
    system("pause");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章