栈和队列基础面试题

本文介绍如何使用两个栈实现带有最小值操作的栈、队列,以及使用两个队列实现栈的方法,并探讨如何判断栈的出入栈序列合法性,还提供了一个数组实现两个栈的高效方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学完了stack和queue的基础接口与模拟实现,接下来我们来一下与栈和队列相关的基础面试题。

一.实现一个栈,要求实现Push(出栈)、Pop(入栈)、Min(返回最小值的操作)的时间复杂度为O(1)
这里我们就需要用两个栈来实现这个功能,一个是主栈s1,一个是辅栈s2,不论插入什么元素,都要在s1中插,s2存储的类型为pair,s2作为辅栈它只存储比先前元素还要小的元素,如果之前已经出现过了这个最小元素了,就在pair的second+1;在删除的时候,s1中都要删除,若s1栈顶元素与s2栈顶元素相等,这表示要删除该栈的当前最小元素了,s2中也要删除,当该元素的second为0时,把它从栈中删除,否则只是将它的second-1;最后Min接口返回的是s2的栈顶元素的first。
这里写图片描述

这里写图片描述
接下来附上代码

//一.实现一个栈,要求实现Push(出栈)、Pop(入栈)、Min(返回最小值的操作)的时间复杂度为O(1)
template <class T>
class StackWithMin
{
public:
    StackWithMin()
    {}

    void Push(const T& x)//插入的时候_s一定插,若小于等于_s栈顶元素,_min也插入该元素
    {
        _s.push(x);
        pair<T, int> p(x, 1);
        if (_min.empty())
        {
            _min.push(p);
        }
        else
        {
            pair<T, int>& top = _min.top();
            if (x <= top.first)
            {
                if (x == top.first)
                {
                    top.second++;
                }
                else
                {
                    _min.push(p);
                }
            }
        }
    }

    void Pop()//删除的时候,_s肯定删除,若_s与_min栈顶元素的first相等,_min也删除
    {
        pair<T, int>& top = _min.top();
        if (_s.top() == top.first)
        {
            top.second--;
            if (top.second == 0)
            {
                _min.pop();
            }
        }
        _s.pop();
    }

    T Min()//返回s2栈顶元素
    {
        if (!_min.empty())
        {
            pair<T, int> top = _min.top();
            return top.first;
        }
    }

    void Print()
    {
        while (!_s.empty())
        {
            cout << "_s: " << _s.top() << " ";
            _s.pop();
        }
        cout << endl;
        while (!_min.empty())
        {
            pair<T, int> top = _min.top();
            cout << "_min: " << top.first<< " ";
            _min.pop();
        }
        cout << endl;
    }
protected:
    stack<T> _s;
    stack<pair<T,int>> _min;
};

void TestStackWithMin()
{
    StackWithMin<int> s;
    s.Push(6);
    cout << s.Min() << endl;
    s.Push(2);
    cout << s.Min() << endl;
    s.Push(4);
    cout << s.Min() << endl;
    s.Push(2);
    cout << s.Min() << endl;
    s.Pop();
    cout << s.Min() << endl;
    s.Pop();
    cout << s.Min() << endl;
    s.Pop();
    cout << s.Min() << endl;
    s.Print();
}

测试结果如下
这里写图片描述

二.使用两个栈实现一个队列
基本的思想也是一个主栈s1,一个辅栈s2。队列与栈不同处在于删除的时候,所以我们只需要在pop的时候做一些工作。首先在插入的时候,全部插入到s1中。在删除的时候,若s2为空,将s1中的元素全部插入到s2中,此时s2栈顶元素即为s1栈底元素,再删除s2栈顶元素;若s2不为空,则直接删除s2栈顶元素。则这样每次pop的顺序就保持了先进先出的队列特性。

代码如下

template <class T>
class TwoStack_Queue
{
public:
    TwoStack_Queue()
    {}

    void Push(const T& x)
    {
        s1.push(x);
    }

    T Pop()
    {
        if (s2.empty())
        {
            assert(!s1.empty());
            while (!s1.empty())
            {
                s2.push(s1.top());
                s1.pop();
            }
        }
        T tmp = s2.top();
        s2.pop();
        return tmp;
    }

protected:
    stack<T> s1, s2;
};

void TestTwoStack_Queue()
{
    TwoStack_Queue<int> s;
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    cout << s.Pop() << " ";
    s.Push(6);
    cout << s.Pop() << " ";
    cout << s.Pop() << " ";
    cout << s.Pop() << " ";
}

测试结果如下
这里写图片描述

三.使用两个队列实现一个栈
同样的,基本的思想也是一个主队列s1,一个辅队列s2。不同的是,这里需要在push的时候做些改动。首先如果两个队列都为空,则先讲元素插入到s1当中。之后若哪个队列为空,先将要插入的元素插入到该队列当中,再将另一不为空的队列中的元素插入到该队列中。Pop操作就是对不为空的队列进行Pop,这样就形成了先进后出的栈的特性。
这里写图片描述
下面附上代码

template <class T>
class TwoQueue_Stack
{
public:
    TwoQueue_Stack()
    {}

    void Push(const T& x)
    {
        if (q1.empty())
        {
            q1.push(x);
            while(!q2.empty())
            {
                q1.push(q2.front());
                q2.pop();
            }
        }
        else
        {
            q2.push(x);
            while (!q1.empty())
            {
                q2.push(q1.front());
                q1.pop();
            }
        }
    }

    T Pop()
    {
        if (!q1.empty())
        {
            T tmp1 = q1.front();
            q1.pop();
            return tmp1;
        }
        if (!q2.empty())
        {
            T tmp2 = q2.front();
            q2.pop();
            return tmp2;
        }
    }
protected:
    queue<T> q1, q2;
};

void TestTwoQueue_Stack()
{
    TwoQueue_Stack<int> q;
    q.Push(1);
    q.Push(2);
    q.Push(3);
    q.Push(4);

    cout << q.Pop() << " ";
    cout << q.Pop() << " ";
    cout << q.Pop() << " ";
    cout << q.Pop() << " ";
}

测试结果如下
这里写图片描述

判断元素出栈、入栈顺序的合法性。如:入栈的序列(1,2,3,4,5),出栈序列为(4,5,3,2,1)是合法序列,入栈的序列(1,2,3,4,5),出栈序列为(1,5,3,2,4)是不合法序列
这个问题我们先附上代码再通过监视窗口分析。

bool Check(int* stack_in,int* stack_out,int len_in,int len_out)
{
    assert(len_in&&len_out);
    if (len_in != len_out)
    //若入栈序列与出栈序列长度不同则出栈序列不合法
    {
        return false;
    }

    stack<int> s;
    int i = 0;
    int j = 0;
    for (; i < len_in; ++i)
    {
        s.push(stack_in[i]);
        //每次进入循环先插入入栈序列的元素
        while (s.size() > 0 && s.top() == stack_out[j])
        //若栈顶元素与出栈序列第一个元素相等,则让栈顶元素出栈,j++
        {
            s.pop();
            j++;
        }
    }
    if (s.size() == 0 &&j == len_in)
    //若j等于出栈序列元素个数,则表示刚好全部出栈,为合法出栈序列
    {
        return true;
    }
    else
    {
        return false;
    }
}

void TestCheck()
{
    int stack_in[] = { 1, 2, 3, 4, 5 };
    int stack_out[] = { 4, 5, 3, 2, 1 };
    int len_in = sizeof(stack_in) / sizeof(stack_in[0]);
    int len_out = sizeof(stack_out) / sizeof(stack_out[0]);
    int ret = Check(stack_in, stack_out, len_in, len_out);
    if (ret == 0)
    {
        cout << "出栈顺序不合法" << endl;
    }
    else
    {
        cout << "出栈顺序合法" << endl;
    }
}

首先每次进入for循环,入栈序列的前三个元素每次入栈后,栈顶元素都不与出栈序列stack_out[j]相等,则全部入栈,即进行了三个for循环。
这里写图片描述
当第四个元素入栈时,栈顶元素和出栈序列stack_out[j]相等,则对栈进行pop操作,j++。
这里写图片描述
进行下一个while循环时,栈顶元素不等于stack_out[j],则退出while循环,进行下一个for循环。
当第五个元素入栈时,还是和出栈序列stack_out[j]相等(主要此时j为1),则s.pop,j++
这里写图片描述
再进行while循环,栈顶元素此时继续等于stack_out[j](此时j=2如上图),s.pop,j++
这里写图片描述
之后栈里的两个元素都是与stack_out[j]分别相等的,所以该出栈序列是合法序列,测试结果如下
这里写图片描述

五.一个数组实现两个栈
这里我们实现一种最节省空间的方案,从数组两边向中间压栈。
这里写图片描述
下面附上代码

template <class T>
class OneArray_TwoStack
{
public:
    OneArray_TwoStack()
        :_a(NULL)
        , _top1(0)
        , _top2(0)
        , _capacity(0)
    {
        _CheckCapacity();
    }

    ~OneArray_TwoStack()
    {
        if (_a)
        {
            delete[] _a;
        }
    }

    void Push1(const T& x)
    {
        _CheckCapacity();
        _a[_top1] = x;
        _top1++;
    }

    void Push2(const T& x)
    {
        _CheckCapacity();
        _a[_top2] = x;
        _top2--;
    }

    void Pop1()
    {
        if (_top1 > 0)
        {
            --_top1;
        }
    }

    void Pop2()
    {
        if (_top2 > 0)
        {
            ++_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];
        }
    }

protected:
    void _CheckCapacity()
    {
        if (_a == NULL)
        {
            _capacity += 3;
            _a = new T[_capacity];
            _top1 = 0;
            _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;j > _top2; --j)
            {
                tmp[j] = _a[j];
            }
            delete[] _a;
            _a = tmp;
            _top2 += _capacity / 2;//让_top2的位置加上一个新增的空间的大小
        }
    }
protected:
    T* _a;
    size_t _top1;
    size_t _top2;
    size_t _capacity;
};

void TestOneArray_TwoStack()
{
    OneArray_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() << " ";
        s.Pop1();
    }
    cout << endl;
    while (!s.Empty2())
    {
        cout << s.Top2() << " ";
        s.Pop2();
    }
    cout << endl;
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值