学完了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;
}