0Pricing
Coding Interview Prep · Aula

Máximo em janela deslizante com deque monotônica

Mantenha uma deque decrescente de índices para responder a consultas do máximo na janela em O(1) por elemento, resolvendo o problema do máximo em janela deslizante em O(n).

Máximo em janela deslizante com deque monotônica é uma aula grátis de Coding Interview Prep no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Coding Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Coding Interview Prep inclui 4 aulas no total.

Problema do máximo em janela deslizante

O problema do máximo em janela deslizante (LeetCode 239) fornece um vetor e um tamanho de janela k. À medida que a janela desliza da esquerda para a direita, uma posição por vez, produza o maior elemento de cada janela. Uma abordagem de força bruta calcula o máximo de cada janela com k elementos em O(k), resultando em O(nk) no total, o que é lento demais para valores grandes de k.

A solução com uma fila monotônica de duas extremidades alcança O(n) no total mantendo uma fila decrescente de índices. A frente sempre contém o índice do máximo da janela atual, fornecendo consultas do máximo em O(1) e permitindo operações tanto na frente quanto atrás.

from collections import deque

# Brute force O(nk) for comparison
def sliding_max_brute(nums, k):
    return [max(nums[i:i+k]) for i in range(len(nums) - k + 1)]

nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
print('Input:', nums, 'k=', k)
print('Expected: [3, 3, 5, 5, 6, 7]')
print('Brute:   ', sliding_max_brute(nums, k))

Fila monotônica de duas extremidades: a ideia principal

Mantenha uma fila monotônica decrescente de duas extremidades que armazena índices (não valores). O invariante é: nums[deque[0]] >= nums[deque[1]] >= ... >= nums[deque[-1]]. Antes de adicionar o índice i:

  • Remova os índices expirados da frente: se deque[0] <= i - k, o índice saiu da janela.
  • Remova os índices menores da parte de trás: enquanto nums[deque[-1]] <= nums[i], esses índices nunca poderão ser o máximo de nenhuma janela futura (estão à esquerda e são menores), portanto descarte-os.

Após essas operações, adicione i ao final. A frente sempre fornece o máximo da janela atual.

from collections import deque

def sliding_window_max(nums, k):
    dq = deque()  # stores indices; values are decreasing
    result = []

    for i, n in enumerate(nums):
        # 1. Remove indices outside the current window
        while dq and dq[0] <= i - k:
            dq.popleft()

        # 2. Remove indices with smaller values from the back
        while dq and nums[dq[-1]] <= n:
            dq.pop()

        dq.append(i)

        # 3. Record max when first full window is complete
        if i >= k - 1:
            result.append(nums[dq[0]])   # front = max of current window

    return result

nums = [1, 3, -1, -3, 5, 3, 6, 7]
print(sliding_window_max(nums, 3))  # [3, 3, 5, 5, 6, 7]

Acompanhando a fila passo a passo

Vamos acompanhar [1, 3, -1, -3, 5, 3, 6, 7] com k=3:

  • i=0 (1): dq=[0]
  • i=1 (3): pop 0 (1<3), dq=[1]
  • i=2 (-1): -1<3, portanto mantenha, dq=[1,2]. Janela [1,3,-1], máximo=nums[1]=3
  • i=3 (-3): -3<-1, dq=[1,2,3]. Verifique a frente: 1 > 3-3=0, OK. Máximo da janela=3
  • i=4 (5): pop 3,2,1 (todos menores), dq=[4]. Frente 4 > 4-3=1, OK. Máximo=5
  • i=5 (3): 3<5, dq=[4,5]. Frente 4 > 5-3=2, OK. Máximo=5
  • i=6 (6): pop 5,4 (ambos menores), dq=[6]. Máximo=6
  • i=7 (7): pop 6, dq=[7]. Máximo=7
from collections import deque

def sliding_window_max_trace(nums, k):
    dq = deque()
    result = []
    for i, n in enumerate(nums):
        while dq and dq[0] <= i - k:
            print(f'  Remove expired index {dq[0]} from front')
            dq.popleft()
        while dq and nums[dq[-1]] <= n:
            print(f'  Remove smaller index {dq[-1]} (val={nums[dq[-1]]}) from back')
            dq.pop()
        dq.append(i)
        print(f'i={i} n={n}: dq={list(dq)} vals={[nums[j] for j in dq]}')
        if i >= k - 1:
            win_max = nums[dq[0]]
            result.append(win_max)
            print(f'  Window {nums[max(0,i-k+1):i+1]} -> max={win_max}')
    return result

nums = [1, 3, -1, -3, 5, 3, 6, 7]
result = sliding_window_max_trace(nums, 3)
print('Result:', result)

Por que cada elemento é inserido e retirado no máximo uma vez

A garantia de O(n) vem do mesmo argumento amortizado da pilha monotônica: cada índice é acrescentado à fila exatamente uma vez e removido (pela frente, quando expira, ou pela parte de trás, quando é substituído) no máximo uma vez. O total de operações na fila durante todo o laço é no máximo 2n.

Os laços internos não aumentam a complexidade geral — qualquer remoção feita neles é 'paga' pela inserção anterior. Esse é o mesmo raciocínio usado para a pilha monotônica, mas estendido a uma fila que permite remoções pelas duas extremidades.

from collections import deque

def sliding_window_max_instrumented(nums, k):
    dq = deque()
    result = []
    front_pops = back_pops = pushes = 0

    for i, n in enumerate(nums):
        while dq and dq[0] <= i - k:
            dq.popleft(); front_pops += 1
        while dq and nums[dq[-1]] <= n:
            dq.pop(); back_pops += 1
        dq.append(i); pushes += 1
        if i >= k - 1:
            result.append(nums[dq[0]])

    print(f'n={len(nums)}: pushes={pushes}, front_pops={front_pops}, back_pops={back_pops}')
    print(f'Total deque ops = {pushes + front_pops + back_pops} <= 3n = {3*len(nums)}')
    return result

import random; random.seed(0)
nums = [random.randint(-100, 100) for _ in range(20)]
sliding_window_max_instrumented(nums, 5)

Mínimo em janela deslizante

O mínimo em janela deslizante é a contrapartida simétrica: mantenha uma fila monotônica crescente de duas extremidades (retire da parte de trás quando o novo elemento for menor que o elemento no final). A frente sempre contém o mínimo da janela atual. Todos os demais passos são idênticos aos da versão do máximo — basta inverter a direção da comparação.

Problemas que pedem o mínimo em uma janela deslizante costumam aparecer como subproblemas dentro de algoritmos maiores. Por exemplo, o custo mínimo para transportar mercadorias ao longo de um caminho com k paradas intermediárias pode exigir o mínimo em janela deslizante sobre vetores de DP.

from collections import deque

def sliding_window_min(nums, k):
    dq = deque()  # increasing monotonic deque
    result = []

    for i, n in enumerate(nums):
        while dq and dq[0] <= i - k:
            dq.popleft()               # expired
        while dq and nums[dq[-1]] >= n:
            dq.pop()                   # pop larger values from back
        dq.append(i)
        if i >= k - 1:
            result.append(nums[dq[0]])  # front = min
    return result

nums = [1, 3, -1, -3, 5, 3, 6, 7]
print('Max k=3:', sliding_window_min.__name__, '->', end=' ')
print(sliding_window_min(nums, 3))   # [-1, -3, -3, -3, 3, 3]

from collections import deque
def sliding_window_max(nums, k):
    dq = deque(); result = []
    for i, n in enumerate(nums):
        while dq and dq[0] <= i-k: dq.popleft()
        while dq and nums[dq[-1]] <= n: dq.pop()
        dq.append(i)
        if i >= k-1: result.append(nums[dq[0]])
    return result

print('Max k=3:', sliding_window_max(nums, 3))   # [3,3,5,5,6,7]

Jogo de saltos VI: DP com fila monotônica de duas extremidades

Jogo de saltos VI (LeetCode 1696) é um exemplo clássico de combinação entre DP e uma fila monotônica de duas extremidades. Dado um vetor e um tamanho máximo de salto k, começando no índice 0, a cada passo você salta de 1 a k posições para a frente, adicionando a pontuação da célula de destino. Maximize a pontuação total. A recorrência de DP é dp[i] = nums[i] + max(dp[i-k], ..., dp[i-1]). Um máximo em janela deslizante sobre o vetor de DP fornece um total de O(n).

Esse padrão — uma recorrência de DP em que cada célula depende do máximo de uma janela de tamanho fixo formada por células anteriores — aparece com frequência e sempre exige uma fila monotônica de duas extremidades.

from collections import deque

def max_result(nums, k):
    n = len(nums)
    dp = [0] * n
    dp[0] = nums[0]
    dq = deque([0])   # indices of max dp values in current window

    for i in range(1, n):
        # Remove expired indices
        while dq and dq[0] < i - k:
            dq.popleft()
        # dp[i] = nums[i] + max dp in window [i-k, i-1]
        dp[i] = nums[i] + dp[dq[0]]
        # Maintain decreasing deque on dp values
        while dq and dp[dq[-1]] <= dp[i]:
            dq.pop()
        dq.append(i)

    return dp[n - 1]

print(max_result([1,-1,-2,4,-7,3], 2))    # 7: path 1->4->3
print(max_result([10,-5,-2,4,0,3], 3))    # 17: path 10->4->3
print(max_result([1,-5,-20,4,-1,3,-6,-3], 2))  # 0

Máximo em janela deslizante: alternativa com árvore de segmentos

Para problemas em que o tamanho da janela varia (não é um k fixo), a fila monotônica de duas extremidades não se aplica diretamente. Em vez disso, use uma tabela esparsa para consultas estáticas de máximo em intervalos, com O(1) por consulta após um pré-processamento de O(n log n), ou uma árvore de segmentos para atualizações dinâmicas, com O(log n) por consulta. No entanto, para janelas deslizantes com k fixo, a fila é imbatível, com O(n).

Em entrevistas, prefira sempre a fila monotônica de duas extremidades O(n) à árvore de segmentos O(n log n) quando o tamanho da janela for constante. Mencione o compromisso: a fila não pode lidar com tamanhos de janela arbitrários nem com atualizações, enquanto as árvores de segmentos podem.

# Sparse table for static RMQ (range maximum query)
import math

def build_sparse_table(arr):
    n = len(arr)
    LOG = int(math.log2(n)) + 1 if n else 1
    table = [[0]*n for _ in range(LOG)]
    table[0] = arr[:]
    j = 1
    while (1 << j) <= n:
        for i in range(n - (1 << j) + 1):
            table[j][i] = max(table[j-1][i], table[j-1][i + (1 << (j-1))])
        j += 1
    return table

def query(table, l, r):
    k = int(math.log2(r - l + 1))
    return max(table[k][l], table[k][r - (1 << k) + 1])

arr = [1, 3, -1, -3, 5, 3, 6, 7]
table = build_sparse_table(arr)
k = 3
result = [query(table, i, i + k - 1) for i in range(len(arr) - k + 1)]
print('Sparse table result:', result)  # [3, 3, 5, 5, 6, 7]

Maior subvetor de uns após excluir um elemento

LeetCode 1493: dado um vetor binário, encontre o comprimento do maior subvetor de 1s após excluir exatamente um elemento (que pode ser 0 ou 1). Este é um problema de janela deslizante. Mantenha uma janela com no máximo um 0. Quando a janela tiver mais de um 0, reduza-a pela esquerda.

Isso usa o padrão de janela deslizante de tamanho variável — não uma fila de duas extremidades. No entanto, ao combiná-lo com a técnica da janela máxima: depois de encontrar todas as janelas válidas, o comprimento máximo é a resposta. A expressão 'excluir um elemento' significa que permitimos exatamente um 0 na nossa janela de 1s.

def longest_subarray(nums):
    left = 0
    zeros = 0
    max_len = 0

    for right in range(len(nums)):
        if nums[right] == 0:
            zeros += 1
        while zeros > 1:
            if nums[left] == 0:
                zeros -= 1
            left += 1
        # Window [left, right] has at most 1 zero
        # After deleting one element, length = right - left (not +1, since we delete one)
        max_len = max(max_len, right - left)

    return max_len

print(longest_subarray([1,1,0,1]))       # 3: delete the 0
print(longest_subarray([0,1,1,1,0,1,1,0,1]))  # 5
print(longest_subarray([1,1,1]))          # 2: must delete one 1

Comparação entre fila de duas extremidades, fila e pilha

Entender quando usar cada contêiner é essencial em entrevistas:

  • Pilha (list): LIFO, acesso por uma única extremidade. Use para DFS, análise de expressões e problemas de pilha monotônica.
  • Fila (deque com appendleft/popleft): FIFO, inserção por uma extremidade e remoção pela outra. Use para BFS e agendamento de tarefas.
  • Fila de duas extremidades: ambas as extremidades são acessíveis em O(1). Use para janelas deslizantes com expiração (remova pela frente) e para o invariante monotônico (remova pela parte de trás). O máximo em janela deslizante é o problema clássico de fila de duas extremidades.

A collections.deque do Python é a ferramenta para os três casos. Use append/pop para o comportamento de pilha e append/popleft ou appendleft/pop para o comportamento de fila ou de fila de duas extremidades.

from collections import deque

# deque as stack
stack = deque()
stack.append(1); stack.append(2); stack.append(3)
print('Stack pop:', stack.pop())  # 3 (LIFO)

# deque as queue
queue = deque()
queue.append(1); queue.append(2); queue.append(3)
print('Queue pop:', queue.popleft())  # 1 (FIFO)

# deque as sliding window with front expiry + back monotonic
dq = deque()
nums = [3, 1, 4, 1, 5, 9, 2, 6]
k = 3
for i, n in enumerate(nums):
    while dq and dq[0] <= i - k: dq.popleft()   # expire front
    while dq and nums[dq[-1]] <= n: dq.pop()     # maintain back
    dq.append(i)
    if i >= k - 1:
        print(f'Window {nums[max(0,i-k+1):i+1]}: max={nums[dq[0]]}')

Subvetor mais curto com soma de pelo menos K: fila de duas extremidades + somas de prefixos

Subvetor mais curto com soma de pelo menos K (LeetCode 862) é um problema avançado que combina somas de prefixos com uma fila monotônica de duas extremidades. Calcule as somas de prefixos e use uma fila para encontrar, para cada extremidade direita, a soma de prefixo mais à esquerda que satisfaz prefix[right] - prefix[left] >= k. A fila mantém somas de prefixos crescentes (retira elementos da parte de trás para preservar a ordem crescente) e retira elementos da frente para coletar respostas válidas.

Este é um dos problemas mais difíceis de janela deslizante porque envolve números negativos (o que descarta a abordagem simples de dois ponteiros) e exige que a fila funcione tanto como estrutura monotônica quanto como mecanismo de expiração.

from collections import deque

def shortest_subarray(nums, k):
    n = len(nums)
    prefix = [0] * (n + 1)
    for i in range(n):
        prefix[i + 1] = prefix[i] + nums[i]

    dq = deque()    # monotonic increasing deque of indices into prefix
    result = float('inf')

    for right in range(n + 1):
        # Pop from front: valid subarrays ending at `right`
        while dq and prefix[right] - prefix[dq[0]] >= k:
            result = min(result, right - dq.popleft())
        # Pop from back: maintain increasing deque
        while dq and prefix[dq[-1]] >= prefix[right]:
            dq.pop()
        dq.append(right)

    return result if result != float('inf') else -1

print(shortest_subarray([1], 1))               # 1
print(shortest_subarray([1, 2], 4))            # -1
print(shortest_subarray([2, -1, 2], 3))        # 3
print(shortest_subarray([84,-37,32,40,95], 167))  # 3

Estratégia de entrevista para problemas com fila de duas extremidades

Identifique um problema de fila monotônica de duas extremidades por estes sinais: (1) você precisa do máximo ou mínimo de uma janela deslizante de tamanho fixo, (2) precisa de uma recorrência de DP dp[i] = f(nums[i], max(dp[i-k..i-1])) ou (3) precisa do índice válido mais próximo que satisfaça uma condição monotônica.

Em entrevistas, escreva a solução com a fila de forma limpa: importe deque, mantenha os dois invariantes (expiração na frente e monotonicidade atrás) e retorne os resultados começando no índice k-1. Mencione sempre a complexidade de tempo O(n) e o espaço O(k) da fila (no máximo k índices armazenados ao mesmo tempo) e compare com a força bruta O(nk) para mostrar a melhoria.

from collections import deque

# Clean, interview-ready template
def sliding_window_max_template(nums, k):
    if not nums or k == 0:
        return []

    dq = deque()   # monotonic decreasing, stores indices
    result = []

    for i in range(len(nums)):
        # Invariant 1: remove expired indices (outside window)
        while dq and dq[0] < i - k + 1:
            dq.popleft()

        # Invariant 2: remove indices with smaller values (useless)
        while dq and nums[dq[-1]] < nums[i]:
            dq.pop()

        dq.append(i)

        # Record result once first full window is established
        if i >= k - 1:
            result.append(nums[dq[0]])

    return result

# Complexity: O(n) time, O(k) space
print(sliding_window_max_template([1,3,-1,-3,5,3,6,7], 3))
print(sliding_window_max_template([1], 1))
print(sliding_window_max_template([], 3))

Verificação rápida

Teste sua compreensão dos conceitos de Estruturas de Dados & Algoritmos — Preparação para Entrevistas de Programação desta lição.

Recapitulação da lição

Nesta lição, você aprendeu: uma fila monotônica decrescente de duas extremidades mantém o máximo da janela na frente enquanto descarta pela parte de trás os elementos menores que as novas chegadas, os índices expirados são removidos da frente quando ultrapassam o limite da janela e cada índice é inserido e retirado no máximo uma vez, resultando em O(n) no total e espaço O(k) para a fila. Em seguida, resolveremos o problema de retenção de água da chuva usando tanto a pilha monotônica quanto a abordagem de dois ponteiros.

Perguntas Frequentes

A aula “Máximo em janela deslizante com deque monotônica” é grátis?

Sim — o texto completo de “Máximo em janela deslizante com deque monotônica” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Coding Interview Prep, atualize para CoddyKit PRO. O curso de Coding Interview Prep inclui 4 aulas no total.

O que vou aprender em “Máximo em janela deslizante com deque monotônica”?

Mantenha uma deque decrescente de índices para responder a consultas do máximo na janela em O(1) por elemento, resolvendo o problema do máximo em janela deslizante em O(n). Você pratica Coding Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Coding Interview Prep?

Nenhuma experiência prévia é necessária. Coding Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Máximo em janela deslizante com deque monotônica”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Coding Interview Prep?

Sim. Cada aula de Coding Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Pilha monotônica: crescente vs decrescente
  2. Maior retângulo no histograma
  3. Máximo em janela deslizante com deque monotônica
  4. Acúmulo de água da chuva: pilha e dois ponteiros
← Voltar para Coding Interview Prep