PrevNext

Sliding Window

From CPH:

A sliding window is a constant-size subarray that moves from left to right through the array.

For each position of the window, we wish to compute some information.

Focus Problem – try your best to solve this problem before continuing!

Implementation

The most straightforward way to do this is to maintain a sorted set of integers containing the integers inside the window. If the window currently spans the range iji \dots j, we observe that sliding the range forward to i+1j+1i+1 \dots j+1 removes aia_i and adds aj+1a_{j+1} to the window. We can support these two operations and query for the minimum / maximum in the set in O(logN)\mathcal{O}(\log N).

Time Complexity: O(NlogN)\mathcal{O}(N\log N)

C++

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;
}

Java

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);

Python

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 ret

Problems

StatusSourceProblem NameDifficultyTags
CSESNormal
Show TagsPrefix Sums, Sliding Window
CSESNormal
Show TagsSet, Sliding Window
CSESHard
Show TagsSet, Sliding Window

With Two Pointers

In general, it isn't required for the subarray to have constant size as long as both the left and right endpoints of the subarray only move to the right.

Focus Problem – try your best to solve this problem before continuing!

Solution

We keep a pointer for the left boundary of the window and expand the right boundary until we find a song already in our subarray. Checking songs can be done with a set.

Then, we erase the left boundary from the set and move the left pointer up by one. We do this until the right boundary is able to expand. The left boundary is removed until the song immediately after the right boundary isn't in our current window's set. The left boundary is removed to provide more flexibility for expanding the right boundary.

The answer is the largest distance between the left and right pointers.

C++

#include <bits/stdc++.h>
using namespace std;
int n;
set<int> s;
int a[200000], ans;
int main() {
int r = -1;
cin >> n;

Java

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;

Python

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].

Problems

StatusSourceProblem NameDifficultyTags
CFEasy
Show Tags2P, Binary Search
GoldEasy
Show TagsSet, Sliding Window
ACEasy
Show TagsSliding Window
CSESEasy
Show Tags2P, Sliding Window
APIONormal
Show TagsMedian, Sliding Window
GoldNormal
Show TagsSliding Window
PlatinumNormal
Show TagsSliding Window
APIOHard
Show TagsDP, Sliding Window
IOIHard
Show TagsBinary Search, DP, Sliding Window
IOIHard
Show TagsDP, Sliding Window

Sliding Window Maximum in O(N)\mathcal{O}(N)

Focus Problem – try your best to solve this problem before continuing!

Resources

Resources
cp-algo

multiple ways to solve this

Method 1 - Deque

Monotonoic Queue

A monotonic queue is one that is always increasing or decreasing. It is implemented by ensuring the newest elements are larger or smaller than the previous elements. If not, the previous elements are popped until the condition is met.

C++

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

Java

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);
}

Python

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])

We keep a decreasing monotonic queue for finding a sliding window maximum. An increasing monotonic queue would only work for finding the sliding window minimum, as it removes large numbers and keeps small numbers.

A decreasing monotonic queue removes elements that are smaller than our current element and only keeps elements larger than our current element in order to maintain monotonicity. In this case, a decreasing monotonic queue works well as any elements smaller than our current element will serve no use to us finding a sliding window maximum.

A monotonic queue is similar to a monotonic stack, but due to its deque implementation, a monotonic queue has access to both the front and end, making it efficient for sliding window problems. A monotonic stack is useful for nearest greater/smaller element.

Deque - Storing Indices

This is method 2 from cp-algo.

The solution is a monotonic queue with the added window size constraints. We print the first element (which is the largest, because the queue is decreasing), and then pop it if it is the boundary of our current window. We pop it because the first element is also the oldest due to the FIFO nature of queues, meaning it must be removed to shift the window right. Then, we remove any numbers that are smaller than our current number to maintain monotonicity.

C++

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);

Java

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);

Python

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()

Deque - Storing Values

Method 1 from CP Algorithms is a similar approach but stores the value itself rather than indices.

Inserting an element is the same process, but deleting an element is different. Because no indices are given this time, we check if the front of the queue is the same as the left boundary of the sliding window. If it is, then we remove it to ensure our window constraints are met.

C++

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()); }

Java

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;
}

Python

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)

Method 2 - Two Stacks

Method 3 from cp-algo. Not as common but nice to know!

We use two stacks s1s_1 and s2s_2 to simulate our maximum queue. Every time we add an element to it, we push the element itself and the maximum in stack s1s_1 after adding this element to s1s_1. To pop out the front element from our queue, we just pop the top element from s2s_2. Since elements in s1s_1 are stored in the order of how we added them, i.e. the last added element is at the top of s1s_1, we just have to pop all of them out and push them into the stack s2s_2 when s2s_2 is empty. After that, these elements in s2s_2 will be in reversed order, i.e. the first added element will be at the top of s2s_2, and we can pop them out as from normal stacks to simulate the dequeue operation of our queue. To find the maximum among all elements in our queue, we just have to return the maximum of both stacks, which is stored in the top element when we added it.

Then, we can solve the problem by removing the first element and adding a new element to the queue to simulate our sliding window. As each operation of our queue takes O(1)\mathcal{O}(1) time, and we add each of NN elements once, we get a time complexity of O(N)\mathcal{O}(N).

C++

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.

Java

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();

Python

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 = []

Problems

StatusSourceProblem NameDifficultyTags
COCINormal
Show TagsSliding Window
YSHard
Show TagsSliding Window
Baltic OIHard
Show TagsSliding Window
POIHard
Show TagsSliding Window
CCVery Hard
Show TagsDP, Sliding Window

Module Progress:

Join the USACO Forum!

Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!

PrevNext