这是一个经典的数据结构问题。问题背后的直觉如下 - 最大值和最小值可以改变的唯一方法是将一个新值压入堆栈或从堆栈中弹出一个新值。鉴于此,假设在堆栈中的每个级别,您都跟踪堆栈中该点处或以下的最大值和最小值。然后,当您将新元素压入堆栈时,您可以通过将刚刚压入的新元素与当前最大值和最小值进行比较,轻松(在 O(1) 时间内)计算堆栈中任意位置的最大值和最小值。类似地,当您弹出一个元素时,您将在堆栈中露出比顶部低一级的元素,该元素已经在堆栈的其余部分中存储了最大值和最小值。
在视觉上,假设我们有一个堆栈并按顺序添加值 2、7、1、8、3 和 9。我们从压入 2 开始,因此我们将 2 压入堆栈。由于 2 现在也是堆栈中的最大值和最小值,因此我们记录以下内容:
2 (max 2, min 2)
现在,让我们推 7。由于 7 大于 2(当前最大值),我们最终得到:
7 (max 7, min 2)
2 (max 2, min 2)
请注意,现在我们可以通过查看堆栈顶部并看到 7 是最大值,2 是最小值来读取堆栈的最大值和最小值。如果我们现在推 1,我们得到
1 (max 7, min 1)
7 (max 7, min 2)
2 (max 2, min 2)
在这里,我们知道 1 是最小值,因为我们可以将 1 与存储在堆栈顶部的缓存最小值 (2) 进行比较。作为一个练习,请确保您理解为什么在添加 8、3 和 9 后,我们会得到:
9 (max 9, min 1)
3 (max 8, min 1)
8 (max 8, min 1)
1 (max 7, min 1)
7 (max 7, min 2)
2 (max 2, min 2)
现在,如果我们想查询最大值和最小值,我们可以在 O(1) 中完成,只需读取堆栈顶部存储的最大值和最小值(分别为 9 和 1)。
现在,假设我们弹出顶部元素。这产生 9 并将堆栈修改为
3 (max 8, min 1)
8 (max 8, min 1)
1 (max 7, min 1)
7 (max 7, min 2)
2 (max 2, min 2)
现在请注意,这些元素的最大值为 8,这正是正确答案!如果我们然后推 0,我们会得到这个:
0 (max 8, min 0)
3 (max 8, min 1)
8 (max 8, min 1)
1 (max 7, min 1)
7 (max 7, min 2)
2 (max 2, min 2)
而且,如您所见,最大值和最小值计算正确。
总体而言,这导致堆栈的实现具有 O(1) 的 push、pop、find-max 和 find-min,它尽可能地渐近。我将把实现留作练习。 :-) 但是,您可能需要考虑使用标准堆栈实现技术之一来实现堆栈,例如使用对象的dynamic array 或linked list,每个对象都包含堆栈元素、最小值和最大值。您可以利用ArrayList 或LinkedList 轻松做到这一点。或者,您可以使用提供的 Java Stack 类,尽管 IIRC 由于此应用程序可能不需要同步,它有一些开销。
有趣的是,一旦您构建了具有这些属性的堆栈,您就可以将其用作构建块来构建a queue with the same properties 和时间保证。您还可以在更复杂的构造中使用它来构建具有这些属性的双端队列。
希望这会有所帮助!
编辑:如果您好奇,我个人有 a min-stack 和上述 min-queue 的 C++ 实现地点。希望这能展示出这在实践中的样子!