在恒定时间内找到堆栈中的最小元素

Posted

技术标签:

【中文标题】在恒定时间内找到堆栈中的最小元素【英文标题】:Finding minumum element in a stack in constant time 【发布时间】:2016-08-12 03:33:11 【问题描述】:

如果我想在堆栈中找到一个最小元素(整数键),那么它可以在恒定时间内完成:

arr = [ 10, 7, 15, 12, 5 ] 

min_stack = []
main_stack = []

def get_min():
    if len(min_stack) < 1:
        return None
    return min_stack[-1]

def push(key):
    main_stack.append(key)
    if len(min_stack) < 1 or min_stack[-1] > key:
        min_stack.append(key)


def pop():
    key = main_stack.pop()
    min = get_min()
    if key == min:
        min_stack.pop()
    return key

for key in arr:
    push(key)    

在上述解决方案中,可以在O(1) 中找到min 值元素,但它使用大小为n 的辅助内存。

有没有一种方法可以在不使用n 大小的内存或说常量内存的情况下通过利用整数键的算术属性来完成。

【问题讨论】:

您只想找到最小元素吗?您可以将最小元素存储在变量中,并在每次推送时更新它。 正如@Haris 所提到的,您可以通过在每次更改堆栈时更新它来保持对最小值的引用——例如如果你推送或弹出你检查的东西,也许会更新。然而,这意味着pop 最终可能在 O(N) 时间内运行,而不是传统堆栈的 O(1)。如果您可以使用堆将所有内容存储在堆栈中,则可以将其降低到 O(logN),但会以内存为代价。 与旧的 min() 相比究竟有什么优势? @KlausD。 -- min 在 O(N) 时间内运行。如果您想在其他操作之上优化 find_min 函数(可能是因为它被调用更多?)那么保留这些数据可能是有利的。 @KlausD。我不想详细讨论这个讨论,但是push() == constant timeget_min() == constant time 所以推动和获得最小的行为是恒定的时间。常数时间是指与堆栈大小相关的时间变化,在此实现中,这些函数的执行时间不随堆栈大小而变化。 【参考方案1】:

如果您只想存储所有推送元素的单个最小值,您可以在没有 O(n) 内存的情况下在 O(1) 中执行此操作。

如果您想存储 min 元素的历史记录,那么除了使用辅助数据结构来保存它们之外别无他法。在这种情况下,使用堆栈是最佳选择,因为您可以在 O(1) 时间推送/弹出,这是您正在使用的方法。

除此之外:您的代码包含一个小错误:

使用您的数组arr = [2, 2] 2 次推送后,min_stack = [2]

当你第一次弹出时,min_stack = []main_stack = [2] 所以get_min() 将返回None,而不是2

要修复它,请更改 push_key:

 if len(min_stack) < 1 or min_stack[-1] >= key:

【讨论】:

如果键是唯一的(正如作者在 cmets 中声称的那样),则不是错误。【参考方案2】:

由于没有另外说明,我想如果在 64 位系统上将被压入堆栈的整数范围限制为 32 位,我会提供一个解决方案。

我知道这些限制可能不适用,但我将把它留在这里以防引发其他想法。

注意如果堆栈值不仅限于整数,那么也可以以类似的方式使用元组,其中 x 的推送将是 (min_thus_far, x) 的推送例子。

arr = [10, 7, 15, 12, 3, 21]

main_stack = []

def get_min():
    if len(main_stack) == 0:
        return None
    return main_stack[-1] >> 32

def push(key):
    current_min = get_min()
    if current_min:
        if key < current_min:
            current_min = key
    else:
        current_min = key
    main_stack.append(key + (current_min << 32))

def pop():
    key = main_stack.pop()
    return key & 0xFFFFFFFF

for key in arr:
    push(key)

def print_state():
    print(", ".join(str(x & 0xFFFFFFFF) for x in main_stack))
    print("min: %d" %(get_min(),))

for _ in arr:
    print_state()
    print "popped:", pop()

输出:

10, 7, 15, 12, 3, 21
min: 3
popped: 21
10, 7, 15, 12, 3
min: 3
popped: 3
10, 7, 15, 12
min: 7
popped: 12
10, 7, 15
min: 7
popped: 15
10, 7
min: 7
popped: 7
10
min: 10
popped: 10

这是一个元组版本:

arr = [10, 7, 15, 12, 3, 21]

main_stack = []

def get_min():
    if len(main_stack) == 0:
        return None
    return main_stack[-1][0]

def push(key):
    current_min = get_min()
    if current_min:
        if key < current_min:
            current_min = key
    else:
        current_min = key
    main_stack.append((current_min, key))

def pop():
    key = main_stack.pop()
    return key[1]

for key in arr:
    push(key)

def print_state():
    print(", ".join(str(x[1]) for x in main_stack))
    print("min: %d" %(get_min(),))

for _ in arr:
    print_state()
    print "popped:", pop()

【讨论】:

我认为从算法上讲,这与 OP 的原始解决方案没有什么不同。它一种通过以稍微更紧凑的方式存储数据来节省内存的聪明方法(通过假设列表中的元素只占用它们可用内存的一半来实现),但它不是t 不同的算法 :-) 我同意,这个概念是完全一样的。答案很大程度上是由关于“利用整数键的算术属性”的询问所引导的。虽然我认为维护一个堆栈比维护两个堆栈更容易,因为重复最小值的错误已经显示。 @sberry 这是一项聪明的技术,绝对比我的解决方案领先一步。

以上是关于在恒定时间内找到堆栈中的最小元素的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode算法题-Min Stack(Java实现)

在 O(1) 时间内检索堆栈中的 Min 元素

有没有办法在少于 O(n) 的时间内找到集合中的最小元素?

155. 最小栈

155. 最小栈

Leetcode刷题Python155. 最小栈