Apuntes DAM
Volver al inicio

Ventana deslizante

AlgoritmosBúsquedaNivel intermedioTambién: sliding window, ventana móvil

Un tramo de elementos seguidos que avanza por el array: al moverse entra uno y sale otro, así que se actualiza en O(1) sin recalcularlo entero. Rachas, medias móviles, subcadenas.

Visualízalo paso a paso

Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.

Ventana deslizante

Escribe un array y el tamaño de la ventana: la mayor suma de k números seguidos, sin volver a sumar la ventana entera en cada paso.

De 2 a 16 enteros
  • dentro de la ventana
  • fuera de juego

Paso 1

La primera ventana, a[0..2], se suma entera: 2 + 1 + 5 = 8.

1static int maxSuma(int[] a, int k) {
2    int suma = 0;
3    for (int i = 0; i < k; i++) suma += a[i];  // suma = 8, mejor = 8
4    int mejor = suma;
5    for (int i = k; i < a.length; i++) {
6        suma += a[i] - a[i - k];
7        if (suma > mejor) mejor = suma;
8    }
9    return mejor;
10}

Variables

suma
8
mejor
8

Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.

La idea

Muchos problemas preguntan por tramos de elementos seguidos: la mejor racha de k días, la media de los últimos 7 valores, la subcadena más larga sin repetir letras. La forma ingenua recalcula cada tramo desde cero: con n posiciones y tramos de tamaño k, O(n·k).

La ventana deslizante aprovecha que dos tramos consecutivos comparten casi todo: al avanzar una posición, entra un elemento por la derecha y sale uno por la izquierda. Si se guarda el resultado de la ventana (su suma, sus letras), basta corregirlo con esos dos: O(1) por paso y O(n) en total.

Hay dos tipos. En la de tamaño fijo, los dos extremos avanzan a la vez (la mejor suma de k seguidos). En la de tamaño variable, el extremo derecho avanza siempre y el izquierdo solo cuando hace falta para que la ventana vuelva a cumplir la condición (la subcadena sin repetidos, el tramo más corto que suma al menos S).

Es un caso particular de los dos punteros: los dos extremos de la ventana son dos índices que solo avanzan hacia la derecha, así que el total de movimientos es como mucho 2n.

Cuándo usarlo

  • Sumas, medias, máximos o recuentos de tramos de k elementos seguidos (medias móviles, rachas).
  • La subcadena o el subarray más largo o más corto que cumple algo (sin repetidos, con suma al menos S, con como mucho k ceros).
  • Procesar datos que llegan en un flujo y solo importan los últimos (los últimos 5 minutos de un sensor).

Cuándo no

  • Si los elementos no tienen que ser seguidos (subconjuntos cualesquiera): no hay ventana que deslizar.
  • En la versión variable con sumas, si puede haber negativos: encoger la ventana puede hacer que la suma suba y la regla deja de valer.

Paso a paso

  1. Primera ventana. Se calcula el resultado de los primeros elementos (en la de tamaño fijo, los k primeros).
  2. Entra uno. El extremo derecho avanza: el nuevo elemento se añade al resultado (suma += a[fin]).
  3. Sale otro. En la de tamaño fijo, sale a[fin − k]; en la variable, el izquierdo avanza mientras la ventana no cumpla (o mientras siga cumpliendo, si se busca la más corta).
  4. Apuntar. Tras cada movimiento se compara con el mejor resultado visto.

El código

La mejor franja de 3 horas

Ventana de tamaño fijo: cada paso suma la hora que entra y resta la que sale.

Java
1public class Main {
2    /** La mayor suma de k elementos seguidos, con una ventana de tamaño fijo que se desliza. */
3    static int maxSuma(int[] a, int k) {
4        int suma = 0;
5        for (int i = 0; i < k; i++) suma += a[i];      // la primera ventana se suma entera
6        int mejor = suma, inicio = 0;
7        for (int i = k; i < a.length; i++) {
8            suma += a[i] - a[i - k];                    // entra uno por la derecha y sale otro por la izquierda
9            if (suma > mejor) { mejor = suma; inicio = i - k + 1; }
10        }
11        System.out.println("La mejor franja de " + k + " horas empieza a las " + inicio + ":00");
12        return mejor;
13    }
14
15    public static void main(String[] args) {
16        int[] visitas = {20, 35, 50, 40, 10, 5, 60, 70, 45, 30, 15, 10};   // visitas por hora
17        System.out.println("Suma de visitas: " + maxSuma(visitas, 3));
18    }
19}

Salida al ejecutarlo (la misma en los 5 lenguajes)

La mejor franja de 3 horas empieza a las 6:00
Suma de visitas: 175

La subcadena más larga sin repetir letras

Ventana de tamaño variable: crece por la derecha y, cuando entra una letra que ya estaba, salta por la izquierda justo detrás de su aparición anterior.

Java
1public class Main {
2    /** La subcadena más larga sin letras repetidas, con una ventana de tamaño variable. */
3    static String sinRepetir(String s) {
4        int[] ultima = new int[65536];                 // última posición (+1) de cada carácter
5        int ini = 0, mejorIni = 0, mejorLong = 0;
6        for (int fin = 0; fin < s.length(); fin++) {
7            char c = s.charAt(fin);
8            ini = Math.max(ini, ultima[c]);             // si c ya estaba en la ventana, se encoge por la izquierda
9            ultima[c] = fin + 1;
10            if (fin - ini + 1 > mejorLong) { mejorLong = fin - ini + 1; mejorIni = ini; }
11        }
12        return s.substring(mejorIni, mejorIni + mejorLong);
13    }
14
15    public static void main(String[] args) {
16        for (String s : new String[] {"abcabcbb", "pwwkew", "murcielago", "aaaa"})
17            System.out.println(s + " → " + sinRepetir(s));
18    }
19}

Salida al ejecutarlo (la misma en los 5 lenguajes)

abcabcbb → abc
pwwkew → wke
murcielago → murcielago
aaaa → a

Traza: mayor suma de 3 seguidos en {2, 1, 5, 1, 3, 2, 9, 1, 4}

VentanaEntraSaleSumaMejor
a[0..2]——88
a[1..3]1278
a[2..4]3199
a[3..5]2569
a[4..6]911414
a[5..7]131214
a[6..8]421414

Cada fila hace una suma y una resta, sea k 3 o 1.000.

Complejidad

ProblemaRecalculando cada tramoVentana deslizante
Mayor suma de k seguidosO(n·k)O(n)
Media móvil de k valoresO(n·k)O(n)
Subcadena más larga sin repetirO(n²) o O(n³)O(n)
Tramo más corto con suma ≥ S (positivos)O(n²)O(n)

Memoria extra: O(1) para sumas; O(alfabeto) o O(k) si hay que recordar qué hay dentro de la ventana.

0102030405015101520tamaño de la entrada (n)operacionesO(n!)O(2ⁿ)O(n²)O(n log n)O(1)O(log n)O(n)
  • Mejor caso: O(n)
  • Caso medio: O(n)
  • Peor caso: O(n)

Cada elemento entra y sale de la ventana una vez; volver a sumar cada ventana sería O(n·k). Las curvas grises son las demás clases, para comparar.

En la práctica

  • Las medias móviles de las gráficas de bolsa, de contagios o de temperaturas.
  • Los limitadores de peticiones de una API (como mucho 100 en el último minuto) y la ventana deslizante del protocolo TCP.
  • La compresión LZ77 (la de ZIP y PNG) busca repeticiones dentro de una ventana de los últimos 32 KB.
  • Detectar rachas en datos de sensores, ventas o tráfico web.

Errores típicos

  • Volver a sumar la ventana entera en cada paso: el resultado es correcto, pero se pierde toda la ventaja.
  • Errores de una posición al calcular quién sale (a[i − k]) o el inicio de la ventana (i − k + 1).
  • No comprobar que k no es mayor que el número de elementos.
  • Usar la ventana variable con sumas y números negativos: la suma ya no crece al ampliar ni baja al encoger.

Ejercicios

Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.

1. La mejor racha de ventas

La primera línea es k y la segunda, las ventas de cada día. Encuentra los k días seguidos con más ventas (con empate, la racha que empieza antes) usando una ventana deslizante. Completa mejorRacha, que devuelve el día en que empieza y la suma.

  • Entrada: 3 y luego 4 2 12 3 8 9 1 5 (ventas enteras de 0 a 999999).
  • Salida: Mejor racha: del día 4 al 6, con 20 ventas (los días se numeran desde 1).
  • Errores: Venta no válida: «x» y k no válido: «…» (tiene que estar entre 1 y N).
☕JavaLa mejor racha de ventasFácil

Ejemplo

Entrada (lo que se escribe por teclado)
3
4 2 12 3 8 9 1 5
Salida esperada
Mejor racha: del día 3 al 5, con 23 ventas
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    /** Inicio de la mejor racha de k días seguidos (la de mayor suma; con empate, la primera) y su suma. */
5    static int[] mejorRacha(int[] v, int k) {
6        int suma = 0;
7        for (int i = 0; i < k; i++) suma += v[i];
8        int mejor = suma, inicio = 0;
9        for (int i = k; i < v.length; i++) {
10            suma += v[i] - v[i - k];
11            if (suma > mejor) {
12                mejor = suma;
13                inicio = i - k + 1;
14            }
15        }
16        return new int[] {inicio, mejor};
17    }
18
19    public static void main(String[] args) {
20        Scanner sc = new Scanner(System.in);
21        String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
22        String segunda = sc.hasNextLine() ? sc.nextLine().trim() : "";
23        String[] t = segunda.isEmpty() ? new String[0] : segunda.split("\\s+");
24        int[] v = new int[t.length];
25        for (int i = 0; i < t.length; i++) {
26            if (!t[i].matches("\\d{1,6}")) {
27                System.out.println("Venta no válida: «" + t[i] + "»");
28                return;
29            }
30            v[i] = Integer.parseInt(t[i]);
31        }
32        if (!primera.matches("[1-9]\\d{0,3}") || Integer.parseInt(primera) > v.length) {
33            System.out.println("k no válido: «" + primera + "» (tiene que estar entre 1 y " + v.length + ")");
34            return;
35        }
36        int k = Integer.parseInt(primera);
37        int[] r = mejorRacha(v, k);
38        System.out.println("Mejor racha: del día " + (r[0] + 1) + " al " + (r[0] + k) + ", con " + r[1] + " ventas");
39    }
40}

Cada día entra en la ventana una vez y sale otra: n sumas y restas en total, da igual lo grande que sea k.

La ventana que termina en el día i empieza en i − k + 1: es el único cálculo con el que hay que tener cuidado.

2. El tramo más corto que llega a una suma

La primera línea es una suma S y la segunda, números enteros positivos. Encuentra el tramo de números seguidos más corto cuya suma sea al menos S (con empate, el que empieza antes). Usa una ventana de tamaño variable: el extremo derecho avanza siempre y, mientras la suma llegue a S, se apunta el tramo y se encoge por la izquierda. Completa masCorto.

  • Entrada: 7 y luego 2 3 1 2 4 3.
  • Salida: El más corto: 2 números (4 + 3 = 7) (1 número), o Ningún tramo llega a 7.
  • Errores: Suma no válida: «…» y Número no válido: «x» (tienen que ser positivos).
☕JavaEl tramo más corto que llega a una sumaDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
7
2 3 1 2 4 3
Salida esperada
El más corto: 2 números (4 + 3 = 7)
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
⏳
Test oculto #6
0/6 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    /** El tramo más corto de números seguidos (positivos) cuya suma llega al menos a s: ventana de tamaño
5        variable que crece por la derecha y se encoge por la izquierda mientras siga llegando.
6        Devuelve {inicio, longitud} (con empate, el primero) o null si ninguno llega. */
7    static int[] masCorto(int[] a, int s) {
8        int ini = 0, suma = 0, mejorIni = -1, mejorLong = Integer.MAX_VALUE;
9        for (int fin = 0; fin < a.length; fin++) {
10            suma += a[fin];
11            while (suma >= s) {
12                if (fin - ini + 1 < mejorLong) {
13                    mejorLong = fin - ini + 1;
14                    mejorIni = ini;
15                }
16                suma -= a[ini++];
17            }
18        }
19        return mejorIni < 0 ? null : new int[] {mejorIni, mejorLong};
20    }
21
22    public static void main(String[] args) {
23        Scanner sc = new Scanner(System.in);
24        String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
25        String segunda = sc.hasNextLine() ? sc.nextLine().trim() : "";
26        if (!primera.matches("[1-9]\\d{0,6}")) {
27            System.out.println("Suma no válida: «" + primera + "»");
28            return;
29        }
30        String[] t = segunda.isEmpty() ? new String[0] : segunda.split("\\s+");
31        int[] a = new int[t.length];
32        for (int i = 0; i < t.length; i++) {
33            if (!t[i].matches("[1-9]\\d{0,5}")) {
34                System.out.println("Número no válido: «" + t[i] + "» (tienen que ser positivos)");
35                return;
36            }
37            a[i] = Integer.parseInt(t[i]);
38        }
39        int s = Integer.parseInt(primera);
40        int[] r = masCorto(a, s);
41        if (r == null) System.out.println("Ningún tramo llega a " + s);
42        else {
43            StringJoiner sj = new StringJoiner(" + ");
44            int suma = 0;
45            for (int i = r[0]; i < r[0] + r[1]; i++) { sj.add(String.valueOf(a[i])); suma += a[i]; }
46            System.out.println("El más corto: " + r[1] + (r[1] == 1 ? " número" : " números") + " (" + sj + " = " + suma + ")");
47        }
48    }
49}

Como todos los números son positivos, ampliar la ventana solo puede subir la suma y encogerla, bajarla: por eso basta mover cada extremo hacia delante, y el total es O(n).

Probar todos los tramos sería O(n²); con negativos, esta ventana no sirve y hace falta otra técnica (sumas acumuladas y búsqueda binaria o una cola monótona).

Test

Test: Ventana deslizante

0/5 respondidas · 0 aciertos

Elige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.

  1. 1.Al deslizar una ventana de tamaño k una posición, ¿cuántos elementos cambian?

  2. 2.¿Qué coste tiene calcular la mayor suma de k seguidos con una ventana deslizante?

  3. 3.En la ventana que termina en la posición i, ¿qué elemento sale al avanzar?

  4. 4.¿Por qué la ventana variable para «suma al menos S» necesita números positivos?

  5. 5.¿Qué técnica es la ventana deslizante en el fondo?

Relacionado