查找无限数字集中最后k个元素的最小数字

时间:2015-04-30 13:40:49

标签: algorithm

  

您将获得无限的号码列表。在最后的k个元素中找到   使用最小复杂度的最低元素(基于值)。

     

注意:一旦最小元素不在k子集中,则为新的最小元素   需要找到。

     

例如:输入:67 45 12 34 90 83 64 86 .... k = 5

     

最初(67 45 12 34 90)将是{k}。随着新输入的进入,{k}   将是(45 12 34 90 83),(12 34 90 83 64),(34 90 83 64 86)......   最低元素分别为12,12和34。

任何人都知道如何解决这个问题?

3 个答案:

答案 0 :(得分:2)

这个问题有一个简单的摊销O(1)算法。

实际上,问题是实现大小为k的队列,其中(摊销)O(1)访问其最小值。使用这样的队列,我们​​可以通过首先使用k Put操作填充队列来处理无限输入,然后通过在放置新元素之前移出最旧的元素来维护队列。

让我们从一个不同的,更简单的问题开始:一个O(1)访问其最小值的堆栈。那是微不足道的。我们需要做的就是保持一堆对:每个堆栈元素由一个数据值和该点的最小值组成。然后堆栈的最小值是与堆栈顶部元素关联的最小值。

现在,快速转移。如果我们只有堆栈数据类型,我们如何有效地实现队列数据类型?答案是所谓的"银行家队列",它使用两个堆栈,我称之为incomingoutgoing。只需按下Put即可实现incomingShift是通过弹出outgoing来实现的,除非它是空的。如果outgoing为空,我们会通过从incoming依次弹出每个元素并将其推送到outgoing来重新填充,这会将incoming转换为outgoing }。

银行家的队列在两个操作中都摊销了O(1),因为(从长远来看),每个元素都被推送并弹出两次,每次从每个堆栈中弹出一次。它是摊销的,因为每隔一段时间(如果队列是固定大小,每k次操作),整个堆栈的元素都会被反转,但这个平均值为O (1)。

现在,我们对原始问题有一个完整的摊销O(1)解决方案:我们使用银行家的队列和两个minstacks。然后,在任何给定时刻,队列的最小值是两个minstack的最小值。

一旦我们有了一般的想法,就有可能提出几个优化。首先,由于我们知道队列的总大小,我们可以使用循环缓冲区来包含两个堆栈。我们可以通过向后存储其中一个堆栈来避免反向操作(尽管我们仍然需要重新计算堆叠的最小值)。最后,我们可以通过注意到我们实际上不需要incoming的MinStack来节省存储空间 - 我们只需知道它当前的最小值 - 而且我们不需要存储值outgoing,我们只需存储最小值。

将所有这些放在一起,一个简单的C ++实现:

template<typename value_type>
class MinBuffer {
  private:
    std::vector<value_type> queue_;
    value_type incoming_min_;
    int index_;
  public:
    MinBuffer(int capacity)
      : queue_(capacity + 1, std::numeric_limits<value_type>::max()),
        incoming_min_(std::numeric_limits<value_type>::max()),
        index_(0) {
      assert(capacity > 0);
    };
    void push_back(const value_type& val) {
      if (index_ == queue_.size() - 1) {
        while (--index_)
          queue_[index_] = std::min(queue_[index_], queue_[index_ + 1]);
        incoming_min_ = std::numeric_limits<value_type>::max();
      }
      queue_[index_++] = val;
      incoming_min_ = std::min(val, incoming_min_);
    }
    value_type getmin() const {
      return std::min(incoming_min_, queue_[index_]);
    }
};

注意:

上述代码使用最大可能值类型作为哨兵,这是Sedgewick的最佳传统;内部队列的最后一个元素始终是sentinel值,它是min函数的标识元素。这节省了大量繁琐的检查和特殊情况。

内部队列的前部(最多但不包括位置index_)是incoming堆栈,顶部位于最高索引处。接下来,直到队列结束,是来自outgoing堆栈的最小值,以相反的顺序存储,以便顶部位于index_。如果outgoing为空,则index_kqueue_[k]始终为哨兵值。

答案 1 :(得分:2)

您也可以在 O(1)分摊时间内通过使用元素及其索引维护deque来执行此操作。

当你看到一个新元素时:

  • 从左侧删除大于它的所有元素。这些元素不再是最小的元素:它们比新元素更早,并且比它更大,因此它们将始终由新元素支配。
  • 删除最右边的元素,如果它太旧(之前添加的元素超过 k 元素)。所有元素都有不同的索引,每个新元素的索引增加1。因此,每次只有一个元素可能变得太旧。
  • 将新元素添加到左侧。

使用这个维护程序,deque总是按元素从右到左排序(即,最右边的元素是最小的),并从左到右按索引排序(因为从左边添加新元素)。

所以最近的最小元素是双端队列中最右边的元素。

更新:所以我似乎想出了这个算法:link。由@Niklas B.提供的链接

这是Python中的一个有效实现:

class BoundedMinTracker:
    def __init__(self, k):
        self._k = k
        self._index = 0
        self._deque = collections.deque()

    def update(self, el):
        while self._deque and self._deque[0][4] >= el:
            self._deque.popleft()
        self._deque.appendleft((self._index, el))
        self._index += 1
        if self._deque[-1][0] < self._index - self._k:
            self._deque.pop()

    def get(self):
        return self._deque[-1][5]

此方法在 O(1)转化时间内进行更新(每个元素仅在deque中添加和删除一次),最差内存使用 O(k),但它通常用得少得多(它不会存储太大而不能成为最小值的元素)

答案 2 :(得分:1)

您可以在最小堆和链表之间实现混合结构。每个堆元素都有一个指向下一个元素的链接。你保持头部和尾部。您可以在尾部添加元素,同时从堆中删除head元素。

每个元素都将在O(log k)时间内处理。

这是Python中的一个例子:

输出:

Pushed:  2, Popped: None, Minimum (last 3):  2
Pushed:  1, Popped: None, Minimum (last 3):  1
Pushed:  3, Popped: None, Minimum (last 3):  1
Pushed:  4, Popped:    2, Minimum (last 3):  1
Pushed:  2, Popped:    1, Minimum (last 3):  2
Pushed: -4, Popped:    3, Minimum (last 3): -4
Pushed:  3, Popped:    4, Minimum (last 3): -4

代码:

class Node:
    def __init__(self, value, index, next):
        self.value = value
        self.index = index
        self.next = next

class LinkedHeap:
    def __init__(self):
        self.V = []
        self.head = self.tail = Node(None, -1, None)

    def count(self):
        return len(self.V)

    def minimum(self):
        return (self.V[0].value if self.count() > 0 else None)

    def push(self, value):
        node = Node(value, len(self.V), None)
        self.tail.next = node
        self.tail = node

        self.V.append(node)
        self.bubble_up(len(self.V)-1)

    def pop(self):
        if not len(self.V): return None

        node = self.head.next
        self.head.next = node.next

        self.V[node.index] = self.V[-1]
        self.V[node.index].index = node.index
        self.V.pop()
        self.bubble_down(node.index)
        return node.value

    def bubble_up(self, n):
        while n != 0 and self.less(n, (n-1)/2):
            self.swap(n, (n-1)/2)
            n = (n-1)/2

    def bubble_down(self, n):
        while self.less(n*2+1, n) or self.less(n*2+2, n):
            c = self.min(n*2+1, n*2+2)
            self.swap(n, c)
            n = c

    def less(self, a, b):
        if a>=self.count(): return False
        if b>=self.count(): return True
        return self.V[a].value<self.V[b].value

    def min(self, a, b):
        return (a if self.less(a,b) else b)

    def swap(self, a, b):
        self.V[a], self.V[b] = self.V[b], self.V[a]
        self.V[a].index = a
        self.V[b].index = b


L = [2, 1, 3, 4, 2, -4, 3]

T = LinkedHeap()

for number in L:
    T.push(number)
    popped = T.pop() if T.count() > 3 else None
    if T.count() > 3:
        T.pop()
    print 'Pushed: {:2}, Popped: {:4}, Minimum (last 3): {:2}'.format(number, popped, T.minimum())