下面的答案是“作弊”,因为尽管操作之间不使用任何空间,但操作本身可以使用空间。请参阅此线程中的其他地方,以获得没有此问题的答案。O(1)
虽然我没有回答您的确切问题,但确实找到了一种算法,该算法可以在时间而不是。我相信这很严格,尽管我没有证据。如果有的话,该算法表明尝试证明的下限是徒劳的,因此它可能有助于回答您的问题。O(n)O(n)O(n−−√)O(n)O(n)
我介绍了两种算法,第一种是Pop的运行时间为的简单算法,第二种是Pop的运行时间为的简单算法。我之所以描述第一个,主要是因为它很简单,所以第二个更容易理解。O (√O(n)O(n−−√)
更详细地说:第一个不使用额外的空间,具有最坏情况(摊销)Push和最坏情况(摊销)Pop,但是最坏情况下的行为并不总是触发。由于它不使用两个队列以外的任何额外空间,因此它比Ross Snider提供的解决方案“略好”。O (n )O(1)O(n)
第二个使用单个整数字段(因此额外空间),在最坏的情况下(并摊销)推入,在摊销Pop。因此,它的运行时间明显比“简单”方法的运行时间长,但它确实占用了一些额外的空间。O (1 )O (√O(1)O(1)O(n−−√)
第一种算法
我们有两个队列:队列和队列。将是我们的“推送队列”,将是已经处于“堆栈顺序”的队列。小号Ë Ç ö Ñ ð ˚F 我ř 小号吨小号È Ç Ò Ñ ðfirstsecondfirstsecond
- 通过简单地将参数放入即可完成推送。first
- 弹出操作如下。如果为空,我们只需将队列出队并返回结果。否则,我们反转,将所有附加到然后交换和。然后我们出队,并返回出队的结果。小号Ë Ç ö Ñ d ˚F 我ř 小号吨小号Ë Ç ö Ñ d ˚F 我ř 小号吨˚F 我ř 小号吨小号Ë Ç ö Ñ d 小号È Ç Ò Ñ dfirstsecondfirstsecondfirstfirstsecondsecond
第一种算法的C#代码
即使您以前从未看过C#,这也应该很容易理解。如果您不知道泛型是什么,只需将所有“ T”实例替换为“字符串”,即可获得一堆字符串。
public class Stack<T> {
private Queue<T> first = new Queue<T>();
private Queue<T> second = new Queue<T>();
public void Push(T value) {
first.Enqueue(value);
}
public T Pop() {
if (first.Count == 0) {
if (second.Count > 0)
return second.Dequeue();
else
throw new InvalidOperationException("Empty stack.");
} else {
int nrOfItemsInFirst = first.Count;
T[] reverser = new T[nrOfItemsInFirst];
// Reverse first
for (int i = 0; i < nrOfItemsInFirst; i++)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
first.Enqueue(reverser[i]);
// Append second to first
while (second.Count > 0)
first.Enqueue(second.Dequeue());
// Swap first and second
Queue<T> temp = first; first = second; second = temp;
return second.Dequeue();
}
}
}
分析
显然,Push在时间内起作用。Pop可能会在和内接触到所有东西恒定的时间,因此在最坏的情况下,我们得到。例如,如果一个将元素压入堆栈,然后依次重复执行单次压入和单个Pop操作,该算法就会表现出这种行为。˚F 我ř 小号吨小号Ë Ç ö Ñ d ø (Ñ )ñO(1)firstsecondO(n)n
第二种算法
我们有两个队列:队列和队列。将是我们的“推送队列”,将是已经处于“堆栈顺序”的队列。小号Ë Ç ö Ñ ð ˚F 我ř 小号吨小号È Ç Ò Ñ ðfirstsecondfirstsecond
这是第一种算法的改编版本,在这种算法中,我们不会立即将“ ”的内容“洗牌” 为。相反,如果与相比包含的元素数量足够少(即,中元素数量的平方根),则我们仅将重新组织为堆栈顺序,而不将其与合并。firstsecondfirstsecondsecondfirstsecond
- 仍然可以通过简单地将参数放入来完成推送。first
- 弹出操作如下。如果为空,我们只需将队列出队并返回结果。否则,我们将重新组织的内容,以便它们按堆栈顺序排列。如果我们只是简单地出队并返回结果。否则,我们追加到,交换和,出列和返回结果。firstsecondfirst|first|<|second|−−−−−−−√firstsecondfirstfirstsecondsecond
第一种算法的C#代码
即使您以前从未看过C#,这也应该很容易理解。如果您不知道泛型是什么,只需将所有“ T”实例替换为“字符串”,即可获得一堆字符串。
public class Stack<T> {
private Queue<T> first = new Queue<T>();
private Queue<T> second = new Queue<T>();
int unsortedPart = 0;
public void Push(T value) {
unsortedPart++;
first.Enqueue(value);
}
public T Pop() {
if (first.Count == 0) {
if (second.Count > 0)
return second.Dequeue();
else
throw new InvalidOperationException("Empty stack.");
} else {
int nrOfItemsInFirst = first.Count;
T[] reverser = new T[nrOfItemsInFirst];
for (int i = nrOfItemsInFirst - unsortedPart - 1; i >= 0; i--)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - unsortedPart; i < nrOfItemsInFirst; i++)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
first.Enqueue(reverser[i]);
unsortedPart = 0;
if (first.Count * first.Count < second.Count)
return first.Dequeue();
else {
while (second.Count > 0)
first.Enqueue(second.Dequeue());
Queue<T> temp = first; first = second; second = temp;
return second.Dequeue();
}
}
}
}
分析
显然,Push在时间内起作用。O(1)
Pop在摊销时间内工作。有两种情况:如果,然后我们在时间中将其随机排列为堆栈顺序。如果,那么我们必须至少有对Push的调用。因此,我们只能在每次调用Push和Pop时都遇到这种情况。在这种情况下,实际的运行时间为,因此摊销时间为。O(n−−√)|first|<|second|−−−−−−−√firstO(|first|)=O(n−−√)|first|≥|second|−−−−−−−√n−−√n−−√O(n)O(nn√)=O(n−−√)
最后说明
通过使Pop 在每次调用时重新组织而不是让Push完成所有工作,可以以使Pop为操作为代价来消除多余的变量。O(n−−√)first