Python入门 LeetCode每日一题 面试题59 - II. 队列的最大值

题目传送门

题目的意思是 在O(1)的事件复杂度内实现,队列的入队列,出队列,求队列最大值

入队列和出队列的时间复杂度本来就是O(1),因此题目中心在求队列最大值

设操作次数为m,value大小的范围为[1,n]

策略1、暴力,每次调用max()函数,显然时间复杂度是O(m×n)

import queue
class MaxQueue:

    def __init__(self):
        self.deque = queue.deque()

    def max_value(self) -> int:
        return max(self.deque) if self.deque else -1

    def push_back(self, value: int) -> None:
        self.deque.append(value)

    def pop_front(self) -> int:
        return self.deque.popleft() if self.deque else -1

策略2,使用辅助列表,分块,将n分为若干块,一般为2块,如果n较大,也可以分多块;设分的块数为k,那么每次求最大值的时间复杂度为O(k×n1/k),总体时间复杂度为O(m×k×n1/k

我的代码是将整体分成了两块,类似于操作系统里面的分层,多层索引
100 × 1000
前面100确定是块,在确定了块之后,再来确定具体的值,需要使用多层for循环。

from queue import Queue


class MaxQueue:

    def __init__(self):
        self.arr = [0 for _ in range(100005)]  # 定义一个数组,大小为10^5,初始化为0
        self.que = Queue(maxsize=0)  # 不设限制
        self.gap = [0 for _ in range(105)]

    def max_value(self) -> int:
        if self.que.empty():
            return -1

        if self.arr[100000] > 0:
            return 100000

        for i in range(99, -1, -1):
            if self.gap[i] > 0:
                for j in range(1000 * i + 999, 1000 * i - 1, -1):
                    if self.arr[j] > 0:
                        return j

    def push_back(self, value: int) -> None:
        self.arr[value] += 1
        self.gap[value // 1000] += 1
        self.que.put(value)

    def pop_front(self) -> int:
        if self.que.empty():
            return -1
        front = self.que.get()
        self.gap[front // 1000] -= 1
        self.arr[front] -= 1
        return front

# Your MaxQueue object will be instantiated and called as such:
# obj = MaxQueue()
# param_1 = obj.max_value()
# obj.push_back(value)
# param_3 = obj.pop_front()

策略3、官方策略-维持单调递减数列

在一个数列中,最大值只对后面子数列的最大值产生影响,而前面子数列的出队并不改变最大值。
例如 1 5 3 8,维持当前最大值为8,那么前面1 5 3出队,最大值不变,此使如果压入4,需要维持一个队列为8 4,当8出队列后,4就是最大值。


官方思路

本算法基于问题的一个重要性质:当一个元素进入队列的时候,它前面所有比它小的元素就不会再对答案产生影响。

举个例子,如果我们向队列中插入数字序列 1 1 1 1 2,那么在第一个数字 2 被插入后,数字 2 前面的所有数字 1 将不会对结果产生影响。因为按照队列的取出顺序,数字 2 只能在所有的数字 1 被取出之后才能被取出,因此如果数字 1 如果在队列中,那么数字 2 必然也在队列中,使得数字 1 对结果没有影响。

按照上面的思路,我们可以设计这样的方法:从队列尾部插入元素时,我们可以提前取出队列中所有比这个元素小的元素,使得队列中只保留对结果有影响的数字。这样的方法等价于要求维持队列单调递减,即要保证每个元素的前面都没有比它小的元素。

那么如何高效实现一个始终递减的队列呢?我们只需要在插入每一个元素 value 时,从队列尾部依次取出比当前元素 value 小的元素,直到遇到一个比当前元素大的元素 value’ 即可。

上面的过程保证了只要在元素 value 被插入之前队列递减,那么在 value 被插入之后队列依然递减。
而队列的初始状态(空队列)符合单调递减的定义。
由数学归纳法可知队列将会始终保持单调递减。
上面的过程需要从队列尾部取出元素,因此需要使用双端队列来实现。另外我们也需要一个辅助队列来记录所有被插入的值,以确定 pop_front 函数的返回值。

保证了队列单调递减后,求最大值时只需要直接取双端队列中的第一项即可。

import queue
class MaxQueue:

    def __init__(self):
        self.deque = queue.deque()
        self.queue = queue.Queue()

    def max_value(self) -> int:
        return self.deque[0] if self.deque else -1


    def push_back(self, value: int) -> None:
        while self.deque and self.deque[-1] < value:
            self.deque.pop()
        self.deque.append(value)
        self.queue.put(value)

    def pop_front(self) -> int:
        if not self.deque:
            return -1
        ans = self.queue.get()
        if ans == self.deque[0]:
            self.deque.popleft()
        return ans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值