【剑指offer】——栈和队列相关练习

一、用两个栈实现队列

(1)题目要求
用两个栈实现一个队列,请实现他的两个函数appendTail 和 deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。

template<typename T>
class CQueue
{
public:
	CQueue(void);
	~CQueue(void);

	void appendTail(const T& node);
	T deleteHead();

private:
	stack<T> stack1;
	stack<T> stack2;
};

(2) 题目分析
我们知道栈结构的特点是“先进后出”,队列结构的特点的“先进先出”;仔细分析这道题,本道题的要求为将两个栈组合起来形成一个“先进先出”结构。

下面,我们通过一个具体的例子来分析往该队列插入和删除元素的全过程。首先,我们往stack1中插入一个元素a,此时stack2中没有元素为空;接着我们再往stack1中继续插入元素b,c;这时的情况如下图1。
这时我们试图完成在队列中删除一个元素的操作,因为队列删除操作要求先进先出,所以按照这个特性,我们应该最先删除的是元素a,但是这时的元素a还在stack1的最底下,所以,我们就要借用我们的stack2来帮助完成操作。我们先把stack1中的所有元素出栈再入栈进stack2,这时stack2的栈顶元素就是a,再一出栈即可,此时的情况如下图2。
在这里插入图片描述
此时,我们就可以总结出出队操作的原理了:当stack2不为空的时候,栈顶元素弹出即为出队元素;当stack2为空的时候,要先将元素压入stack1当中再把stack1当中的元素挨个儿弹入stack2。

我再在上述操作的基础上,再出队一个元素b,情况如下图3。为了验证我们的结论是否正确,我们在情况3的情况下入队一个元素d,再出队,情况变化过程如下图4
在这里插入图片描述
有了上述描述,我们可以写出代码如下:

template<typename T>
void CQueue<T>::appendTail(const T& node)
{
	stack1.push(node);
}

template<typename T>
T CQueue<T>::deleteHead()
{
	if (stack2.size() <= 0)
	{
		while (stack1.size() > 0)
		{
			T& data = stack1.top();
			stack1.pop();
			stack2.push(data);
		}
	}
	if (stack2.size() == 0)
	{
		throw new exception("queue is empty");
	}
	T head = stack2.top();
	stack2.pop();
	return head;
}

二、用两个队列实现一个栈

和上一题的分析相同,要解答这道题的核心就是拿“先进先出”的结构实现“先进后出”的过程。其实本质就是两个队列之间的来回入队和出队操作,具体过程如下图所示:
在这里插入图片描述
入栈过程就是一个入队的过程,但是出队时首先要出队的元素是最后入栈的元素d,所以这时需要另外一个队列来保存d前面的a,b,c三个元素。出队元素c的过程也是同样。这时再入栈一个元素e,再使其出栈。那么还是应该将队列中元素e的前一个元素出队并入队对列2,再队列一出队元素e。
有了对上述过程的描述,我们可以得出代码如下:

template<typename T> class CStack
{
public:
	CStack(void);
	~CStack(void);

	void appendTail(const T& node);
	T deleteHead();

private:
	queue<T> q1;
	queue<T> q2;
};

template<typename T> 
void CStack<T>::appendTail(const T& node)
{
	if (!q1.empty())
		q1.push(node);
	else
		q2.push(node);
}

template<typename T>
T CStack<T>::deleteHead()
{
	int ret = 0;
	if (!q1.empty())
	{
		int num = q1.size();
		while (num > 1)
		{
			q2.push(q1.front());
			q1.pop();
			num--;
		}
		ret = q1.front;
		q1.pop();
	}
	else
	{
		int num = q2.size();
		while (num > 1)
		{
			q1.push(q2.front());
			q2.pop();
			num--;
		}
		ret = q2.front;
		q2.pop();
	}
	return ret;
}

另外,还有使用一个队列实现一个栈和一个栈实现一个队列的解法,具体参考我写的另外一篇博文栈和队列的相互实现

三、包含min函数的栈

(1) 题目要求
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
(2)题目分析
方法一:
首先,一拿到这道题,你可能想的是每次压入一个新元素进栈的时候,将栈里的所有元素都排序,然后返回最小值即为栈顶元素的值,这样就保证了时间复杂度应为O(1),但是仔细想一想这种思路不能保证最后压入栈的元素能够最先的出栈,不符合栈的数据结构
方法二:
添加一个成员变量来存放最小的元素,每次入栈的时候如果入栈的元素比该成员变量的值小,则更新该成员变量的值,但是你有想过如果把该最小值出栈了过后如何确定剩余栈里面的最小值呢
方法三:
基于第二种方法的思路,我们可以把成员变量改成一个辅助栈,专门来存放栈中的最小值。具体的操作,我们用一个确切的例子来加以描述。下面的表格表示了栈内压入3,4,2,1之后接连两次弹出栈顶数字再压入0时,数据栈辅助栈和最小值的相关情况。
在这里插入图片描述
从上表可以看出,如果每次都把最小元素压入辅助栈,那么就能保证辅助栈的栈顶一直都是最小元素。
基于上述思路,我们实现pop,push和min的核心代码如下:

template <typename T>
void StackWithMin<T>::push(const T& value)
{
	m_data.push(value);

	if (m_min.size() == 0 || value < m_min.top())
		m_min.push(value);
	else
		m_min.push(m_min.top());
}

template <typename T>
void StackWithMin<T>::pop()
{
	assert(m_data.size() > 0 && m_min.size() > 0);

	m_data.pop();
	m_min.pop();
}

template <typename T>
const T& StackWithMin<T>::min() const
{
	assert(m_data.size() > 0 && m_min.size() > 0);
	return m_min.top();
}

四、栈的压入、弹出序列

(1)题目要求
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
(2)题目分析
我们整个的分析过程就还是以具体的序列4,5,3,2,1为弹出序列来加以分析。首先,我们想要弹出的数字是4,因此4需要向压入辅助栈,压入顺序由压栈序列来确定,所以要先把1,2,3压入辅助栈,此时辅助栈里面的元素是1,2,3,4,刚好栈顶的元素就是我们想要弹出的数字4,把4在辅助栈里面弹出。接着,我们想要弹出的数字是5,他不是此时辅助栈的栈顶元素,因此需要把压栈序列中的5压入辅助栈,这时5位于栈顶,可以被弹出来,接着进行下面同上的操作。图示如下:
在这里插入图片描述
另一方面,我们以具体的序列4,3,5,1,2为弹出序列来加以分析。前面的匹配过程和上面描述的相同,但是到最后当弹出数字是1的时候,栈顶元素不匹配并且压栈序列里面也没有元素了,所以该序列不可能是一个弹出序列
在这里插入图片描述
有了上述两个方面的具体分析,我们可以写出代码如下:

bool IsPopOrder(const int* Ppush, const int* Ppop, int nlength)
{
	bool bpossible = false;
	if (Ppush != nullptr && Ppop != nullptr && nlength > 0)
	{
		const int* pNextPush = Ppush;
		const int* pNextPop = Ppop;

		stack<int> stackData;//定义一个辅助栈

		while (pNextPop - Ppop < nlength)
		{
			while (stackData.empty() || stackData.top() != *pNextPop)
			{
				if (pNextPush - Ppush == nlength)//如果已经把压栈元素全部压入
					break;

				stackData.push(*pNextPush);//如果辅助栈中的栈顶元素不等于弹出序列Ppop的栈顶元素
				pNextPush++;
			}
			if (stackData.top() != *pNextPop)//如果所有数字都压入过后,仍然没有找到下一个弹出的数字
				break;

			stackData.pop();//匹配成功,辅助栈弹出寻找下一个弹出的数字
			pNextPop++;
		}

		if (stackData.empty() && pNextPop - Ppop == nlength)
			bpossible = true;//辅助栈为空并且弹出序列遍历完毕
	}
	return bpossible;
}
发布了98 篇原创文章 · 获赞 9 · 访问量 3642

猜你喜欢

转载自blog.csdn.net/qq_43412060/article/details/105486224