Ventana deslizante
Ventana deslizante
De CPH:
Una ventana deslizante es un subarreglo de tamaño constante que se mueve de izquierda a derecha a través del arreglo.
Para cada posición de la ventana, queremos computar alguna información.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| LC | Sliding Window Maximum | Fácil | Sliding Window | en el módulo |
Implementación
La forma más directa de hacer esto es mantener un conjunto ordenado de enteros que contiene los enteros dentro de la ventana. Si la ventana actualmente cubre el rango , observamos que deslizar el rango hacia adelante a elimina y añade a la ventana. Podemos soportar estas dos operaciones y consultar el mínimo / máximo del conjunto en .
Complejidad temporal:
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
multiset<int> s;
vector<int> ret;
for (int i = 0; i < k; i++) { s.insert(nums[i]); }
for (int i = k; i < nums.size(); i++) {
ret.push_back(*s.rbegin());
s.erase(s.find(nums[i - k]));
s.insert(nums[i]);
}
ret.push_back(*s.rbegin());
return ret;
}static TreeMap<Integer, Integer> multiset = new TreeMap<Integer, Integer>();
static void add(int x) {
if (multiset.containsKey(x)) {
multiset.put(x, multiset.get(x) + 1);
} else {
multiset.put(x, 1);
}
}
static void remove(int x) {
multiset.put(x, multiset.get(x) - 1);
if (multiset.get(x) == 0) { multiset.remove(x); }
}
static ArrayList<Integer> maxSlidingWindow(int[] nums, int k) {
ArrayList<Integer> ret = new ArrayList<Integer>();
for (int i = 0; i < k; i++) { add(nums[i]); }
for (int i = k; i < nums.length; i++) {
ret.add(multiset.lastKey());
remove(nums[i - k]);
add(nums[i]);
}
ret.add(multiset.lastKey());
return ret;
}from sortedcontainers import SortedList
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
s = SortedList(nums[:k])
ret = []
for i in range(k, len(nums)):
ret.append(s[-1])
s.remove(nums[i - k])
s.add(nums[i])
ret.append(s[-1])
return retProblemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Max Subarray Sum II | Normal | Sliding Window, Prefix Sums | Solución | |
| CSES | Sliding Median | Normal | Sliding Window, Set | Solución | |
| CSES | Sliding Cost | Difícil | Sliding Window, Set | Solución |
Con dos punteros
En general, no se exige que el subarreglo tenga tamaño constante siempre que tanto el extremo izquierdo como el derecho del subarreglo solo se muevan hacia la derecha.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Playlist | Fácil | 2P | en el módulo |
Solución
Mantenemos un puntero para el borde izquierdo de la ventana y expandimos el borde derecho hasta encontrar una canción que ya esté en nuestro subarreglo. Comprobar las canciones se puede hacer con un conjunto.
Luego, borramos el borde izquierdo del conjunto y movemos el puntero izquierdo una posición hacia adelante. Hacemos esto hasta que el borde derecho pueda expandirse. El borde izquierdo se elimina hasta que la canción inmediatamente posterior al borde derecho no esté en el conjunto de nuestra ventana actual. El borde izquierdo se elimina para dar más flexibilidad al expandir el borde derecho.
La respuesta es la mayor distancia entre los punteros izquierdo y derecho.
#include <bits/stdc++.h>
using namespace std;
int n;
set<int> s;
int a[200000], ans;
int main() {
int r = -1;
cin >> n;
for (int i = 0; i < n; i++) { cin >> a[i]; }
for (int i = 0; i < n; i++) {
while (r < n - 1 && !s.count(a[r + 1])) s.insert(a[++r]);
ans = max(ans, r - i + 1);
s.erase(a[i]);
}
cout << ans;
}public class Playlist {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
StringTokenizer st = new StringTokenizer(br.readLine());
int a[] = new int[n];
for (int i = 0; i < n; i++) a[i] = Integer.parseInt(st.nextToken());
int r = -1;
HashSet<Integer> s = new HashSet<Integer>();
int ans = 0;
for (int i = 0; i < n; i++) {
while (r < n - 1 && !s.contains(a[r + 1])) s.add(a[++r]);
ans = Math.max(ans, r - i + 1);
s.remove(a[i]);
}
System.out.println(ans);
}
}n = int(input().strip())
nums = [int(v) for v in input().split(" ")]
ans = -float("inf")
left, right = 0, 0
unique_songs = set()
while right < n:
# Notice that all the songs in unique_songs are unique in each iteration.
# We keep this property by shrinking the window before inserting nums[right].
while nums[right] in unique_songs:
unique_songs.remove(nums[left])
left += 1
unique_songs.add(nums[right])
right += 1
# right - left is the window size.
ans = max(ans, right - left)
print(ans)Problemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CF | K-Good Segment | Fácil | 2P, Binary Search | Solución | |
| Gold | Haybale Feast | Fácil | Set, Sliding Window | Solución | |
| AC | Mex Min | Fácil | Sliding Window | Solución | |
| CSES | Subarray Distinct Values | Fácil | Sliding Window, 2P | Solución | |
| APIO | 2015 - Palembang Bridges | Normal | Sliding Window, Median | Solución | |
| Gold | ★ Painting the Barn | Normal | Sliding Window | Solución | |
| Platinum | Fort Moo | Normal | Sliding Window | Solución | |
| POI | Little Bird | Normal | Sliding Window, DP | — | |
| APIO | 2009 - Digging for Oil | Difícil | Sliding Window, DP | Solución | |
| IOI | 2005 - Garden | Difícil | Sliding Window, Binary Search, DP | Solución | |
| IOI | 2006 - Pyramid | Difícil | Sliding Window, DP | Solución |
Máximo de ventana deslizante en
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| LC | Sliding Window Maximum | Fácil | Sliding Window | en el módulo |
Recursos
| Fuente | Recurso | Notas |
|---|---|---|
| cp-algo | Minimum stack / Minimum queue | varias formas de resolver esto |
Método 1 - Deque
| Fuente | Recurso | Notas |
|---|---|---|
| CPH | 8.3 - Sliding Window Minimum |
Cola monótona
Una cola monótona es una que siempre es creciente o decreciente. Se implementa asegurando que los elementos más nuevos sean mayores o menores que los elementos anteriores. Si no, se extraen los elementos anteriores hasta que se cumpla la condición.
vector<int> nums{1, 2, 3, 5, 4};
deque<int> increasing;
deque<int> decreasing;
for (int e : nums) {
while (!increasing.empty() && increasing.back() > e) { increasing.pop_back(); }
increasing.push_back(e);
}
// 1 2 3 4
for (int e : increasing) { cout << e << " "; }
for (int e : nums) {
while (!decreasing.empty() && decreasing.back() < e) { decreasing.pop_back(); }
decreasing.push_back(e);
}
// 5 4
for (int e : decreasing) { cout << e << " "; }int[] nums = {1, 2, 3, 5, 4};
ArrayDeque<Integer> increasing = new ArrayDeque<Integer>();
ArrayDeque<Integer> decreasing = new ArrayDeque<Integer>();
for (int e : nums) {
while (!increasing.isEmpty() && increasing.peekLast() > e) {
increasing.pollLast();
}
increasing.addLast(e);
}
System.out.println(increasing); // [1, 2, 3, 4]
for (int e : nums) {
while (!decreasing.isEmpty() && decreasing.peekLast() < e) {
decreasing.pollLast();
}
decreasing.addLast(e);
}
System.out.println(decreasing); // [5, 4]nums = [1, 2, 3, 5, 4]
increasing = collections.deque()
decreasing = collections.deque()
for e in nums:
while increasing and increasing[-1] > e:
increasing.pop()
increasing.append(e)
print(increasing) # deque([1, 2, 3, 4])
for e in nums:
while decreasing and decreasing[-1] < e:
decreasing.pop()
decreasing.append(e)
print(decreasing) # deque([5, 4])Mantenemos una cola monótona decreciente para hallar el máximo de una ventana deslizante. Una cola monótona creciente solo serviría para hallar el mínimo de la ventana deslizante, porque elimina números grandes y conserva números pequeños.
Una cola monótona decreciente elimina elementos que son menores que nuestro elemento actual y solo conserva elementos mayores que nuestro elemento actual para mantener la monotonía. En este caso, una cola monótona decreciente funciona bien porque cualquier elemento menor que nuestro elemento actual no nos servirá para hallar el máximo de la ventana deslizante.
Una cola monótona es similar a una pila monótona , pero por su implementación con deque, una cola monótona tiene acceso tanto al frente como al final, lo que la hace eficiente para problemas de ventana deslizante. Una pila monótona es útil para el elemento más cercano mayor/menor.
Deque - Guardar índices
Este es el método 2 de cp-algo.
La solución es una cola monótona con las restricciones adicionales del tamaño de la ventana. Imprimimos el primer elemento (que es el más grande, porque la cola es decreciente) y luego lo extraemos si es el borde de nuestra ventana actual. Lo extraemos porque el primer elemento también es el más antiguo por la naturaleza FIFO de las colas, lo que significa que hay que eliminarlo para desplazar la ventana a la derecha. Luego, eliminamos cualquier número que sea menor que nuestro número actual para mantener la monotonía.
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
vector<int> ret;
deque<int> d;
for (int i = 0; i < nums.size(); i++) {
if (!d.empty() && d.front() <= i - k) { d.pop_front(); }
while (!d.empty() && nums[d.back()] < nums[i]) { d.pop_back(); }
d.push_back(i);
if (i >= k - 1) { ret.push_back(nums[d.front()]); }
}
return ret;
}public int[] maxSlidingWindow(int[] nums, int k) {
int[] ret = new int[nums.length - k + 1];
ArrayDeque<Integer> d = new ArrayDeque<Integer>();
for (int i = 0; i < nums.length; i++) {
if (!d.isEmpty() && d.peekFirst() <= i - k) { d.pollFirst(); }
while (!d.isEmpty() && nums[d.peekLast()] < nums[i]) { d.pollLast(); }
d.addLast(i);
if (i >= k - 1) { ret[i - k + 1] = nums[d.peekFirst()]; }
}
return ret;
}def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
d = collections.deque()
ret = []
for i, num in enumerate(nums):
if d and d[0] <= i - k:
d.popleft()
while d and nums[d[-1]] < num:
d.pop()
d.append(i)
if i >= k - 1:
ret.append(nums[d[0]])
return retDeque - Guardar valores
El método 1 de CP Algorithms es un enfoque similar pero guarda el valor mismo en lugar de los índices.
Insertar un elemento es el mismo proceso, pero eliminar un elemento es distinto. Como esta vez no se dan índices, comprobamos si el frente de la cola es igual al borde izquierdo de la ventana deslizante. Si lo es, entonces lo eliminamos para asegurar que se cumplan las restricciones de nuestra ventana.
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
vector<int> res;
deque<int> d;
for (int i = 0; i < nums.size(); i++) {
while (!d.empty() && d.back() < nums[i]) { d.pop_back(); }
d.push_back(nums[i]);
if (i >= k - 1) { res.push_back(d.front()); }
if (i >= k - 1 && !d.empty() && d.front() == nums[i - k + 1]) { d.pop_front(); }
}
return res;
}public int[] maxSlidingWindow(int[] nums, int k) {
int[] ret = new int[nums.length - k + 1];
ArrayDeque<Integer> d = new ArrayDeque<Integer>();
for (int i = 0; i < nums.length; i++) {
while (!d.isEmpty() && d.peekLast() < nums[i]) {
d.pollLast();
}
d.addLast(nums[i]);
if (i >= k - 1) { ret[i - k + 1] = d.peekFirst(); }
if (i >= k - 1 && !d.isEmpty() && d.peekFirst() == nums[i - k + 1]) {
d.pollFirst();
}
}
return ret;
}def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
d = collections.deque()
ret = []
for i, num in enumerate(nums):
while d and d[-1] < num:
d.pop()
d.append(num)
if i >= k - 1:
ret.append(d[0])
if i >= k - 1 and d and d[0] == nums[i - k + 1]:
d.popleft()
return retMétodo 2 - Dos pilas
Método 3 de cp-algo. ¡No es tan común pero es bueno saberlo!
Usamos dos pilas y para simular nuestra cola de máximos. Cada vez que añadimos un elemento, empujamos el elemento mismo y el máximo de la pila después de añadir este elemento a . Para extraer el elemento del frente de nuestra cola, simplemente extraemos el elemento de la cima de . Como los elementos de se guardan en el orden en que los añadimos, es decir, el último elemento añadido está en la cima de , solo tenemos que extraerlos todos y empujarlos a la pila cuando está vacía. Después de eso, esos elementos en estarán en orden invertido, es decir, el primer elemento añadido estará en la cima de , y podemos extraerlos como de pilas normales para simular la operación de dequeue de nuestra cola. Para hallar el máximo entre todos los elementos de nuestra cola, solo tenemos que devolver el máximo de ambas pilas, que se guarda en el elemento de la cima cuando lo añadimos.
Luego, podemos resolver el problema eliminando el primer elemento y añadiendo un elemento nuevo a la cola para simular nuestra ventana deslizante. Como cada operación de nuestra cola toma tiempo , y añadimos cada uno de los elementos una vez, obtenemos una complejidad temporal de .
struct MaxQueue {
/**
* For each pair<int, int> e, e.first is the value of the element and
* e.second is the maximum among all elements in that stack under element e.
*/
stack<pair<int, int>> s1, s2;
/**
* Get the maximum element in the MaxQueue.
* It is the maximum of both stacks which are stored in s.top().second.
*/
int query() {
if (s1.empty() && s2.empty()) { return -1e9; }
if (s1.empty() || s2.empty()) {
return s1.empty() ? s2.top().second : s1.top().second;
}
return max(s1.top().second, s2.top().second);
}
/**
* Add a new element into our MaxQueue. We add the value of this
* element itself and the maximum element in the stack s1 after adding.
*/
void enqueue(int val) {
s1.push({val, max(val, (s1.empty() ? val : s1.top().second))});
}
/**
* Remove the first element from our MaxQueue by popping the top
* element from s2.
*/
void dequeue() {
if (s2.empty()) {
// Move all elements from s1 to s2 when s2 is empty
while (!s1.empty()) {
int mx = s2.empty() ? s1.top().first : s2.top().second;
s2.push({s1.top().first, max(mx, s1.top().first)});
s1.pop();
}
}
s2.pop();
}
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k) {
MaxQueue q;
vector<int> max_vals;
// Fill the queue with elements from the first window
for (int i = 0; i < k; i++) { q.enqueue(nums[i]); }
max_vals.push_back(q.query());
/*
* We slide the window to the right by removing the first element
* from the queue and adding the new element at the end of the queue.
* For each window, we add the maximum to our result array max_vals.
*/
for (int i = k; i < nums.size(); i++) {
q.dequeue();
q.enqueue(nums[i]);
max_vals.push_back(q.query());
}
return max_vals;
}
};class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MaxQueue q = new MaxQueue();
int n = nums.length;
int[] maxVals = new int[n - k + 1];
// Fill the queue with elements from the first window
for (int i = 0; i < k; i++) { q.enqueue(nums[i]); }
maxVals[0] = q.query();
/*
* We slide the window to the right by removing the first element
* from the queue and adding the new element at the end of the queue.
* For each window, we add the maximum to our result array maxVals.
*/
for (int i = k; i < n; i++) {
q.dequeue();
q.enqueue(nums[i]);
maxVals[i - k + 1] = q.query();
}
return maxVals;
}
private static class MaxQueue {
/**
* For each Pair e, e.a is the value of the element and
* e.b is the maximum among all elements in that stack under element e.
*/
public Stack<Pair> s1, s2;
public MaxQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
/**
* Get the maximum element in the MaxQueue.
* It is the maximum of both stacks which are stored in s.peek().b.
*/
public int query() {
if (s1.empty() && s2.empty()) { return -1000000000; }
if (s1.empty() || s2.empty()) {
return s1.empty() ? s2.peek().b : s1.peek().b;
}
return Math.max(s1.peek().b, s2.peek().b);
}
/**
* Add a new element into our MaxQueue. We add the value of this
* element itself and the maximum element in the stack s1 after adding.
*/
public void enqueue(int val) {
s1.push(new Pair(val, s1.empty() ? val : Math.max(val, s1.peek().b)));
}
/**
* Remove the first element from our MaxQueue by popping the top
* element from s2.
*/
public void dequeue() {
if (s2.empty()) {
// Move all elements from s1 to s2 when s2 is empty
while (!s1.empty()) {
int mx = s2.empty() ? s1.peek().a : s2.peek().b;
s2.push(new Pair(s1.peek().a, Math.max(mx, s1.peek().a)));
s1.pop();
}
}
s2.pop();
}
}
private static class Pair {
public int a, b;
public Pair(int a, int b) {
this.a = a;
this.b = b;
}
}
}class MaxQueue:
def __init__(self):
"""
For each pair (val, max_val) in the stacks:
- val: The value of the element.
- max_val: The maximum among all elements in that stack under element val.
"""
self.s1 = []
self.s2 = []
def query(self) -> int:
"""
Get the maximum element in the MaxQueue.
It is the maximum of both stacks, stored in the second value of the top of the stack.
"""
if not self.s1 and not self.s2:
return float("-inf")
if not self.s1 or not self.s2:
return self.s2[-1][1] if not self.s1 else self.s1[-1][1]
return max(self.s1[-1][1], self.s2[-1][1])
"""
Add a new element into the MaxQueue. We add the value of this
element and the maximum element in stack s1 after adding.
"""
def enqueue(self, val: int):
max_val = max(val, self.s1[-1][1]) if self.s1 else val
self.s1.append((val, max_val))
"""
Remove the first element from our MaxQueue by popping the top
element from s2.
"""
def dequeue(self):
# Move all elements from s1 to s2 when s2 is empty
if not self.s2:
while self.s1:
val = self.s1.pop()[0]
max_val = max(val, self.s2[-1][1]) if self.s2 else val
self.s2.append((val, max_val))
self.s2.pop()
class Solution:
def maxSlidingWindow(self, nums: list[int], k: int) -> list[int]:
q = MaxQueue()
max_vals = []
# Fill the queue with elements from the first window
for i in range(k):
q.enqueue(nums[i])
max_vals.append(q.query())
"""
We slide the window to the right by removing the first element
from the queue and adding the new element at the end of the queue.
For each window, we add the maximum to our result array max_vals.
"""
for i in range(k, len(nums)):
q.dequeue()
q.enqueue(nums[i])
max_vals.append(q.query())
return max_valsProblemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| COCI | Razlika | Normal | Sliding Window | — | |
| YS | ★ Queue Composite | Difícil | Sliding Window | Solución | |
| Baltic OI | 2015 - Hacker | Difícil | Sliding Window | Solución | |
| POI | Pilots | Difícil | Sliding Window | — | |
| CC | Binary Land | Muy difícil | Sliding Window, DP | Solución |