单调队列的两种思路:强单调和弱单调

共同点是都关注队列首位是最大的。
不同点在于(假设窗口大小为3):

  • 弱单调只要首位最大,一个元素如果保持最大返回3次,那必然满了(队列长度到3了)。所以满了就是返回了3次的情况,这时候pop掉首位
    但弱单调去掉首位,剩下的不一定弱单调,所以还需要维护剩下的成为弱单调(625->25,不单调了)
  • 强单调是严格单调,队列满了,则必然已经返回3次了,但返回3次不一定会满(比如6,62,625->65,这在弱单调里就满了)。但好在返回3次是有解法的,即和num(i-3)比较。这就是强单调的好处(在写法上不需要维护单调性)。

但两种的复杂度是没有多大区别的,别看写法上强单调for循环内只有一个while,弱单调for循环内有两个while。

  • 强单调在进入队列的时候是和s[-1]比较的,也就是说从最小的比到最大的,多次比较
    弱单调则只比较一次,和最大的比,如果要出队列,则全出,直接一次赋值[]即可
  • 强单调在处理满了或已返回3次的情况,就方便了,只需要一次pop
    弱单调之前入栈很方便,但积累了“弱性”/“不严格单调性”,在处理满了的情况,就爆发了,在这需要多次比较的pop,以维护一个弱单调性队列(625->25->5)

所以这两种方法的本质不同在于,对元素出入队列的处理的方式/时机的不同,而不影响复杂度。
从每个元素本身考虑,最多出入队列各一次,遍历所有队列,所以时间复杂度不超过O(2n)

  1. 弱单调 (如[6,2]将5进入队列后变成[6, 2, 5],只要首位最大)
class Solution:
    def maxInWindows(self , num: List[int], size: int) -> List[int]:
        if size > len(num) or not size : return None
        if size == len(num): return [max(num)]
        if size == 1: return num

        # 两个列表一个存储结果,一个作弱单调队列,只保存从大到小的结果
        res, s = [], []

        # 未形成窗口期:先把头 size-1 个数存入单调队列中。
        for i in range(size - 1):
            if not s:
                s.append(num[i])
            else:
                # 都是和s[0]比较,所以是弱单调
                # 小的直接入队,大的则要先出队,再判断直到小的或者空的,才入队
                while len(s) and s[0] <= num[i]:
                    s = []
                s.append(num[i])

        # 形成窗口期:循环后len-(size-1)个数,,每轮循环都存结果到res里面
        for i in range(size - 1, len(num)):
            # 满了的情况,先出队。出队后不一定弱单调添加一轮循环,去掉不符合顺序的
            if len(s) == size:
                s.pop(0)
                j = 0
                while len(s) > 1 and j <= len(s) - 2 and s[0] <= s[1]:
                    s.pop(0)
            # 维护弱单调队列
            while len(s) and s[0] <= num[i]:
                s = []
            s.append(num[i])
            # 每轮返回s[0]
            res.append(s[0])

        return res
  1. 强单调(没有[6, 2, 5],只会退化为[6, 5],严格递减)
class Solution:
    def maxInWindows(self , num: List[int], size: int) -> List[int]:
        if size > len(num) or not size : return None
        if size == len(num): return [max(num)]
        if size == 1: return num

        res, s = [], []

        for i in range(size - 1):
            if not s:
                s.append(num[i])
            else:
                # 和s[-1]比较,所以是强单调,严格递减。
                while len(s) and s[-1] <= num[i]:
                    s.pop()
                s.append(num[i])

        for i in range(size - 1, len(num)):
            if s[0] == num[i - size]:
                s.pop(0)
            while len(s) and s[-1] <= num[i]:
                s.pop()
            s.append(num[i])
            res.append(s[0])

        return res