0Pricing
Coding Interview Prep · Lección

Patrón de pila monótona

Aplique la pila monótona para resolver daily-temperatures, largest-rectangle-in-histogram y next-greater-element en O(n).

Patrón de pila monótona es una lección gratuita de Coding Interview Prep en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Coding Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Coding Interview Prep incluye 4 lecciones en total.

¿Qué es una pila monótona?

Una pila monótona es una pila que mantiene un invariante ordenado entre sus elementos. Una pila monótona creciente tiene elementos que aumentan de abajo arriba; una pila monótona decreciente tiene elementos que disminuyen de abajo arriba. Cuando un elemento nuevo infringe el invariante, se extraen elementos hasta restaurarlo y, después, se inserta el elemento nuevo.

Este mecanismo sencillo permite responder en O(n) a consultas sobre el «elemento mayor más cercano» y el «elemento menor más cercano», que ingenuamente requerirían bucles anidados de O(n²).

# Build a monotonically increasing stack from [3,1,2,5,4]
nums  = [3, 1, 2, 5, 4]
stack = []
for n in nums:
    while stack and stack[-1] > n:
        stack.pop()   # remove elements that violate increasing order
    stack.append(n)
    print('stack:', stack)

Siguiente elemento mayor (LeetCode 496)

Para cada elemento, encuentre el primer elemento estrictamente mayor a su derecha. Una solución de fuerza bruta O(n²) recorre hacia la derecha desde cada posición. El enfoque de la pila monótona consiste en mantener una pila decreciente de índices. Cuando se encuentra un elemento mayor, se extraen todos los índices de elementos menores: el «siguiente elemento mayor» de estos es el elemento actual. Los índices restantes no tienen un siguiente elemento mayor (su respuesta es -1).

def nextGreaterElement(nums):
    n      = len(nums)
    result = [-1] * n
    stack  = []   # indices, decreasing values
    for i, val in enumerate(nums):
        while stack and nums[stack[-1]] < val:
            j = stack.pop()
            result[j] = val
        stack.append(i)
    return result

print(nextGreaterElement([2, 1, 2, 4, 3]))   # [4, 2, 4, -1, -1]
print(nextGreaterElement([1, 3, 2, 4]))       # [3, 4, 4, -1]

Siguiente elemento mayor en un array circular

LeetCode 503, «Next Greater Element II»: el mismo problema, pero el array se considera circular. Después de llegar al final, vuelva al principio y continúe comprobando. El truco consiste en recorrer el array dos veces (índices de 0 a 2n-1) y utilizar i % n para indexar el array original. Introduzca únicamente índices del rango [0, n-1] en la pila para evitar procesarlos dos veces.

def nextGreaterElements(nums):
    n      = len(nums)
    result = [-1] * n
    stack  = []
    for i in range(2 * n):
        while stack and nums[stack[-1]] < nums[i % n]:
            j = stack.pop()
            result[j] = nums[i % n]
        if i < n:
            stack.append(i)
    return result

print(nextGreaterElements([1, 2, 1]))   # [2, -1, 2]
print(nextGreaterElements([5, 4, 3, 2, 1]))  # [-1, 5, 5, 5, 5]

Temperaturas diarias: solución completa

Repaso de LeetCode 739: para cada día, ¿cuántos días deben transcurrir hasta encontrar una temperatura más cálida? La pila monótona contiene los índices de los días cuyas temperaturas están en orden decreciente. Cuando se encuentra un día más cálido i, se extraen de la pila todos los índices j de días más fríos y se registra result[j] = i - j. Los días que quedan en la pila nunca encontraron un día más cálido, por lo que su resultado permanece en 0.

def dailyTemperatures(temperatures):
    n      = len(temperatures)
    result = [0] * n
    stack  = []  # indices, decreasing temperatures
    for i, t in enumerate(temperatures):
        while stack and temperatures[stack[-1]] < t:
            j         = stack.pop()
            result[j] = i - j
        stack.append(i)
    return result

temps = [73, 74, 75, 71, 69, 72, 76, 73]
print(dailyTemperatures(temps))
# [1, 1, 4, 2, 1, 1, 0, 0]

Elemento menor anterior

La consulta del «elemento menor anterior» pregunta: para cada elemento, ¿cuál es el valor menor más cercano a su izquierda? Utilice una pila monótona creciente y procese los elementos de izquierda a derecha. Antes de insertar el índice i, la cima de la pila es el elemento menor anterior, porque todos los elementos mayores que nums[i] ya se eliminaron durante inserciones anteriores, cuando elementos mayores provocaron su extracción.

def previousSmallerElement(nums):
    n      = len(nums)
    result = [-1] * n
    stack  = []   # indices, increasing values
    for i, val in enumerate(nums):
        while stack and nums[stack[-1]] >= val:
            stack.pop()
        if stack:
            result[i] = nums[stack[-1]]
        stack.append(i)
    return result

print(previousSmallerElement([4, 5, 2, 10, 8]))  # [-1, 4, -1, 2, 2]
print(previousSmallerElement([3, 1, 2]))           # [-1, -1, 1]

Rectángulo más grande en un histograma

LeetCode 84, «Largest Rectangle in Histogram»: utilice una pila monótona creciente de índices. Para cada barra, extraiga todas las barras más altas que la actual. Para cada barra extraída h, su límite derecho es el índice actual i y su límite izquierdo es la nueva cima de la pila + 1 (o 0 si la pila está vacía). Área = h × (right - left). Añada un centinela de altura 0 para forzar la extracción de todas las barras restantes al final.

def largestRectangleArea(heights):
    heights = heights + [0]  # sentinel
    stack   = []  # indices, increasing heights
    result  = 0
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            left   = stack[-1] + 1 if stack else 0
            width  = i - left
            result = max(result, height * width)
        stack.append(i)
    return result

print(largestRectangleArea([2, 1, 5, 6, 2, 3]))  # 10
print(largestRectangleArea([2, 4]))                # 4
print(largestRectangleArea([1]))                   # 1

Rectángulo máximo (LeetCode 85)

LeetCode 85, «Maximal Rectangle», extiende el problema del histograma a una matriz binaria 2D. Para cada fila, calcule las alturas acumuladas de las barras: si matrix[row][col] == '1', la altura es el número de 1 consecutivos situados encima de esta celda e incluyéndola. Después, aplique el algoritmo del «rectángulo más grande en un histograma» al array de alturas de cada fila. Tiempo: O(m × n) para una matriz de m×n.

def maximalRectangle(matrix):
    if not matrix or not matrix[0]:
        return 0
    n       = len(matrix[0])
    heights = [0] * n
    result  = 0

    def largest_in_hist(h):
        h = h + [0]
        stack, best = [], 0
        for i, val in enumerate(h):
            while stack and h[stack[-1]] > val:
                height = h[stack.pop()]
                left   = stack[-1] + 1 if stack else 0
                best   = max(best, height * (i - left))
            stack.append(i)
        return best

    for row in matrix:
        for j, cell in enumerate(row):
            heights[j] = heights[j] + 1 if cell == '1' else 0
        result = max(result, largest_in_hist(heights[:]))
    return result

m = [['1','0','1','0','0'],['1','0','1','1','1'],
     ['1','1','1','1','1'],['1','0','0','1','0']]
print(maximalRectangle(m))  # 6

Trapping Rain Water: enfoque con pila

LeetCode 42, «Trapping Rain Water», con una pila: mantenga una pila decreciente de índices. Cuando se encuentra una barra más alta, se forma un valle. Extraiga el fondo del valle; calcule el ancho del agua como (current_index - stack_top - 1) y la altura como (min(current_bar, new_stack_top_bar) - valley_height). Sume todas las contribuciones. Tiempo: O(n), espacio: O(n).

def trap(height):
    stack  = []
    water  = 0
    for i, h in enumerate(height):
        while stack and height[stack[-1]] < h:
            bottom     = stack.pop()
            if not stack:
                break
            left       = stack[-1]
            width      = i - left - 1
            bounded_h  = min(h, height[left]) - height[bottom]
            water     += width * bounded_h
        stack.append(i)
    return water

print(trap([0,1,0,2,1,0,1,3,2,1,2,1]))  # 6
print(trap([4,2,0,3,2,5]))               # 9

Reconocimiento de problemas de pilas monótonas

Estas son señales de que una pila monótona es la herramienta adecuada: el problema pide el siguiente o anterior elemento mayor o menor, la respuesta para cada elemento depende de elementos situados en una dirección concreta, o una solución ingenua de O(n²) implica recorrer hacia la izquierda o la derecha para cada elemento. La pila almacena candidatos que podrían ser respuestas para elementos futuros y los descarta en cuanto aparece un candidato mejor.

Decida siempre de antemano: creciente (para el siguiente o anterior elemento menor) o decreciente (para el siguiente o anterior elemento mayor), y desde qué dirección procesará los elementos.

Análisis amortizado O(n)

Al principio, los algoritmos de pila monótona parecen ser O(n log n) u O(n²) debido al bucle while dentro del bucle for. Sin embargo, cada elemento se inserta como máximo una vez y se extrae como máximo una vez. El número total de operaciones de inserción es n y el número total de operaciones de extracción también es como máximo n. Por tanto, en todas las iteraciones el trabajo total es de 2n operaciones: O(n) amortizado, no O(n²).

# Count total pushes and pops for n=1000
n     = 1000
nums  = list(range(n, 0, -1))  # worst case for decreasing stack
stack = []
pushes = pops = 0
for val in nums:
    while stack and stack[-1] < val:
        stack.pop()
        pops += 1
    stack.append(val)
    pushes += 1

print(f'n={n}, pushes={pushes}, pops={pops}, total={pushes+pops}')
# Total <= 2*n

Resumen: elección del invariante de la pila monótona

Elija la dirección de la pila según la consulta. Para el siguiente elemento mayor, utilice una pila decreciente: extraiga elementos cuando el elemento actual sea mayor. Para el siguiente elemento menor, utilice una pila creciente: extraiga elementos cuando el elemento actual sea menor. Para el rectángulo más grande, utilice una pila creciente y extraiga elementos cuando aparezca una barra más baja. Para el máximo de una ventana deslizante, utilice una deque decreciente y elimine elementos por ambos extremos.

Escribir el invariante en un comentario antes de programar aclara la lógica y agiliza la depuración.

Comprobación rápida

Compruebe su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.

Repaso de la lección

En esta lección ha aprendido: una pila monótona mantiene un invariante ordenado extrayendo los elementos que lo infringen antes de insertar el elemento nuevo, las pilas decrecientes responden a consultas sobre el siguiente elemento mayor; las pilas crecientes responden a consultas sobre el siguiente elemento menor, y el tiempo total es O(n) amortizado porque cada elemento se inserta y se extrae como máximo una vez. A continuación implementaremos colas utilizando pilas y pilas utilizando colas.

Preguntas frecuentes

¿La lección «Patrón de pila monótona» es gratis?

Sí — el texto completo de «Patrón de pila monótona» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Coding Interview Prep, actualiza a CoddyKit PRO. El curso de Coding Interview Prep incluye 4 lecciones en total.

¿Qué aprenderé en «Patrón de pila monótona»?

Aplique la pila monótona para resolver daily-temperatures, largest-rectangle-in-histogram y next-greater-element en O(n). Practicas Coding Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Coding Interview Prep?

No se requiere experiencia previa. Coding Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Patrón de pila monótona»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Coding Interview Prep?

Sí. Cada lección de Coding Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Implementación y aplicaciones de pilas
  2. Implementación de colas y deque
  3. Patrón de pila monótona
  4. Simulación mutua de pilas y colas
← Volver a Coding Interview Prep