我有兴趣创建一个类似于堆栈的Java数据结构,它尽可能高效地支持以下操作:
- Push,它在堆栈顶部添加一个新元素,
- Pop,它删除堆栈顶部的元素,
- Find-Max,它返回(但不删除)堆栈的最大元素,
- Find-Min,返回(但不删除)堆栈的最小元素,以及
此数据结构的最快实现是什么?我该如何用 Java 编写它?
I am interested in creating a Java data structure similar to a stack that supports the following operations as efficiently as possible:
- Push, which adds a new element atop the stack,
- Pop, which removes the top element of the stack,
- Find-Max, which returns (but does not remove) the largest element of the stack, and
- Find-Min, which returns (but does not remove) the smallest element of the stack, and
What would be the fastest implementation of this data structure? How might I go about writing it in Java?
发布评论
评论(4)
这是一道经典的数据结构题。问题背后的直觉如下 - 最大值和最小值可以改变的唯一方法是将新值推入堆栈或从堆栈中弹出新值。鉴于此,假设在堆栈中的每个级别,您都跟踪堆栈中该点或低于该点的最大值和最小值。然后,当您将新元素压入堆栈时,您可以通过将刚刚压入的新元素与当前的最大值和最小值进行比较,轻松(在 O(1) 时间内)计算堆栈中任何位置的最大值和最小值。类似地,当您弹出一个元素时,您将在堆栈中暴露该元素,该元素比顶部低一级,该元素已经在旁边存储了堆栈其余部分中的最大值和最小值。
从视觉上看,假设我们有一个堆栈并按顺序添加值 2、7、1、8、3 和 9。我们首先将 2 压入堆栈,然后将 2 压入堆栈。由于 2 现在也是堆栈中的最大和最小值,因此我们记录以下内容:
现在,让我们压入 7。由于 7 大于 2(当前最大值),我们最终得到以下结果:
请注意,现在我们可以读取通过查看堆栈顶部并看到 7 是最大值,2 是最小值,可以从堆栈的最大值和最小值中取出。如果我们现在压入 1,我们会得到
这里,我们知道 1 是最小值,因为我们可以将 1 与存储在堆栈顶部的缓存最小值 (2) 进行比较。作为练习,请确保您理解为什么在添加 8、3 和 9 后,我们会得到以下结果:
现在,如果我们想要查询最大值和最小值,我们可以通过读取存储的最大值来在 O(1) 中完成此操作和 min 位于堆栈顶部(分别为 9 和 1)。
现在,假设我们弹出顶部元素。这会产生 9 并将堆栈修改为
现在请注意,这些元素的最大值是 8,这正是正确的答案!如果我们然后压入 0,我们会得到这样的结果:
并且,正如您所看到的,最大值和最小值计算正确。
总体而言,这导致堆栈的实现具有 O(1) 的入栈、出栈、find-max 和 find-min,这是渐近所能达到的最佳效果。我将把实施作为练习。 :-) 但是,您可能需要考虑使用一种标准堆栈实现技术来实现堆栈,例如使用 动态数组或对象的链接列表,每个对象都保存堆栈元素,最小值,和最大。您可以通过利用
ArrayList
或LinkedList
轻松完成此操作。或者,您可以使用提供的 JavaStack
类,尽管 IIRC 由于同步而产生一些开销,而该应用程序可能不需要这些开销。有趣的是,一旦您使用这些属性构建了堆栈,您就可以将其用作构建块来构建 具有相同属性和时间保证的队列。您还可以在更复杂的构造中使用它来构建具有这些属性的双端队列。
编辑:如果你好奇,我有 最小堆栈和前面提到的min-queue 在我的个人网站上。希望这能展示这在实践中可能是什么样子!
This is a classic data structures question. The intuition behind the problem is as follows - the only way that the maximum and minimum can change is if you push a new value onto the stack or pop a new value off of the stack. Given this, suppose that at each level in the stack you keep track of the maximum and minimum values at or below that point in the stack. Then, when you push a new element onto the stack, you can easily (in O(1) time) compute the maximum and minimum value anywhere in the stack by comparing the new element you just pushed to the current maximum and minimum. Similarly, when you pop off an element, you will expose the element in the stack one step below the top, which already has the maximum and minimum values in the rest of the stack stored alongside it.
Visually, suppose that we have a stack and add the values 2, 7, 1, 8, 3, and 9, in that order. We start by pushing 2, and so we push 2 onto our stack. Since 2 is now the largest and smallest value in the stack as well, we record this:
Now, let's push 7. Since 7 is greater than 2 (the current max), we end up with this:
Notice that right now we can read off the max and min of the stack by looking at the top of the stack and seeing that 7 is the max and 2 is the min. If we now push 1, we get
Here, we know that 1 is the minimum, since we can compare 1 to the cached min value stored atop the stack (2). As an exercise, make sure you understand why after adding 8, 3, and 9, we get this:
Now, if we want to query the max and min, we can do so in O(1) by just reading off the stored max and min atop the stack (9 and 1, respectively).
Now, suppose that we pop off the top element. This yields 9 and modifies the stack to be
And now notice that the max of these elements is 8, exactly the correct answer! If we then pushed 0, we'd get this:
And, as you can see, the max and min are computed correctly.
Overall, this leads to an implementation of the stack that has O(1) push, pop, find-max, and find-min, which is as asymptotically as good as it gets. I'll leave the implementation as an exercise. :-) However, you may want to consider implementing the stack using one of the standard stack implementation techniques, such as using a dynamic array or linked list of objects, each of which holds the stack element, min, and max. You could do this easily by leveraging off of
ArrayList
orLinkedList
. Alternatively, you could use the provided JavaStack
class, though IIRC it has some overhead due to synchronization that might be unnecessary for this application.Interestingly, once you've built a stack with these properties, you can use it as a building block to construct a queue with the same properties and time guarantees. You can also use it in a more complex construction to build a double-ended queue with these properties as well.
EDIT: If you're curious, I have C++ implementations of a min-stack and a the aforementioned min-queue on my personal site. Hopefully this shows off what this might look like in practice!
虽然答案是正确的,但我们可以做得更好。如果堆栈有很多元素,那么我们就浪费了很多空间。但是,我们可以按如下方式保存这些无用的空间:
我们可以使用两个堆栈,而不是保存每个元素的最小值(或最大值)。因为最小值(或最大值)的变化不会那么频繁,所以只有当新值是
<=
(或>=
) 到当前的最小值(或最大值)。下面是 Java 中的实现:
请注意,使用这种方法,我们在
minStack
中的元素会非常少。maxStack
,从而节省空间。例如Although the answer is right, but we can do better. If the stack has lot of elements, then we are wasting a lot of space. However, we can save this useless space as follow:
Instead of saving min(or max) value with each element, we can use two stacks. Because change in the minimum(or maximum) value will not be so frequent, we push the min(or max) value to its respective stack only when the new value is
<=
(or>=
) to the current min(or max) value.Here is the implementation in
Java
:Note that using this approach, we would have very few elements in
minStack
&maxStack
, thus saving space. e.g.可能来不及回复,但只是为了记录。这是java代码。
堆栈类:
May be too late to reply but just for the sake of record. Here is the java code.
Stack Class:
使用链接列表:
Using Linkedlist: