1.4.31 #
解答 #
三个栈分别命名为左中右。
左侧栈和右侧栈负责模拟队列,和用两个栈模拟队列的方法类似。
由于是双向队列,左栈和右栈会频繁的倒来倒去,因此每次都只倒一半的元素可以有效减少开销。
有一侧栈为空时,另一侧栈中上半部分先移动到中间栈中,下半部分倒到另一侧栈里,再从中间栈拿回上半部分元素。
这样可以确保接下来的 pop 操作一定是常数级别的。
代码 #
internal class Deque<TItem>
{
private readonly Stack<TItem> _left;
private readonly Stack<TItem> _middle;
private readonly Stack<TItem> _right;
/// <summary>
/// 构造一条新的双向队列。
/// </summary>
public Deque()
{
_left = new Stack<TItem>();
_middle = new Stack<TItem>();
_right = new Stack<TItem>();
}
/// <summary>
/// 向双向队列左侧插入一个元素。
/// </summary>
/// <param name="item">要插入的元素。</param>
public void PushLeft(TItem item)
{
_left.Push(item);
}
/// <summary>
/// 向双向队列右侧插入一个元素。
/// </summary>
/// <param name="item">要插入的元素。</param>
public void PushRight(TItem item)
{
_right.Push(item);
}
/// <summary>
/// 当一侧栈为空时,将另一侧的下半部分元素移动过来。
/// </summary>
/// <param name="source">不为空的栈。</param>
/// <param name="destination">空栈。</param>
private void Move(Stack<TItem> source, Stack<TItem> destination)
{
var n = source.Size();
// 将上半部分元素移动到临时栈 middle
for (var i = 0; i < n / 2; i++)
{
_middle.Push(source.Pop());
}
// 将下半部分移动到另一侧栈中
while (!source.IsEmpty())
{
destination.Push(source.Pop());
}
// 从 middle 取回上半部分元素
while (!_middle.IsEmpty())
{
source.Push(_middle.Pop());
}
}
/// <summary>
/// 检查双端队列是否为空。
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return _right.IsEmpty() && _middle.IsEmpty() && _left.IsEmpty();
}
/// <summary>
/// 从右侧弹出一个元素。
/// </summary>
/// <returns></returns>
public TItem PopRight()
{
if (_right.IsEmpty())
{
Move(_left, _right);
}
return _right.Pop();
}
/// <summary>
/// 从左侧弹出一个元素。
/// </summary>
/// <returns></returns>
public TItem PopLeft()
{
if (_left.IsEmpty())
{
Move(_right, _left);
}
return _left.Pop();
}
/// <summary>
/// 返回双端队列的大小。
/// </summary>
/// <returns></returns>
public int Size()
{
return _left.Size() + _middle.Size() + _right.Size();
}
}