Sliding Window: Handling Non-Invertible Operators
Разница между en3 и en4, 0 символ(ов) изменены
# Sliding Window↵

The sliding window technique is a powerful approach for solving problems involving subarrays. It works by maintaining a subarray (window) of elements and moving (sliding) this window across the data to compute results efficiently.↵

---↵

## Introduction↵

When I first learned the sliding-window trick for **sum**, it felt like magic: out went an O(n·K) double loop, in came an O(n) solution. Then I tried to do the same for **minimum** and promptly got stuck. You can’t “subtract” a minimum!↵

In this tutorial you’ll learn:  ↵
1. How **invertible** operators (sum, XOR) let you slide in O(n)  ↵
2. Why **non-invertible** operators (min, max, GCD…) break the simple trick  ↵
3. A general O(n) approach using **two stacks** and **aggregation**↵

---↵

## 1. Invertible Operators: Sum & XOR↵

If you can **undo** your operation in O(1), the sliding window is trivial.↵

### 1.1 Fixed-Size Window Sum↵

```cpp↵
vector<int> slideSum(const vector<int>& a, int K) {↵
    int n = a.size(), sum = 0;↵
    vector<int> ans;↵
    // initial window↵
    for (int i = 0; i < K; ++i)↵
        sum += a[i];↵
    ans.push_back(sum);↵
    // slide: add new, subtract old↵
    for (int i = K; i < n; ++i) {↵
        sum += a[i];          // enter↵
        sum -= a[i - K];      // leave↵
        ans.push_back(sum);↵
    }↵
    return ans;↵
}↵
```↵

### 1.2 Fixed-Size Window XOR↵

```cpp↵
vector<int> slideXor(const vector<int>& a, int K) {↵
    int n = a.size(), xr = 0;↵
    vector<int> ans;↵
    // initial window↵
    for (int i = 0; i < K; ++i)↵
        xr ^= a[i];↵
    ans.push_back(xr);↵
    // slide: include new, exclude old↵
    for (int i = K; i < n; ++i) {↵
        xr ^= a[i];          // enter↵
        xr ^= a[i - K];      // leave↵
        ans.push_back(xr);↵
    }↵
    return ans;↵
}↵
```↵

**Why it works:** `x ^ x = 0`, re-XOR the outgoing element undoes it in O(1).↵

---↵

## 2. The Challenge: Non-Invertible Operators↵

For **Min / Max**, **Bitwise OR / AND**, **GCD / LCM** etc., there is no efficient “remove” step.  ↵

For **Min / Max**; We can use multisets, but that will make the solution O(N log(N))↵

For **Bitwise OR / AND**, **GCD / LCM**: We can't use multisets to get our answer, we will need some other data structures like sparse table / segment trees.↵

Let's look at an easier and efficient idea to handle all of these operations:↵

**Insight:** A sliding window acts as a queue  ↵
- **push** an element at the back when a new element enters  ↵
- **pop** an element from the front when the old one leaves  ↵

What if our queue could also answer “what’s the min?” in O(1)?↵

---↵

## 3. Implement a Queue with Two Stacks↵

A simple, classic trick to get **amortized O(1)** push/pop operations is using two stacks:↵

```cpp↵
#include <stack>↵
using namespace std;↵

struct MyQueue {↵
    stack<int> in, out;↵

    // Push a new element into the queue.↵
    void push(int x) {↵
        in.push(x);↵
    }↵

    // Pop the oldest element from the queue.↵
    void pop() {↵
        if (out.empty()) {↵
            while (!in.empty()) {↵
                out.push(in.top());↵
                in.pop();↵
            }↵
        }↵
        out.pop();↵
    }↵

    // Return the oldest element.↵
    int front() {↵
        if (out.empty()) {↵
            while (!in.empty()) {↵
                out.push(in.top());↵
                in.pop();↵
            }↵
        }↵
        return out.top();↵
    }↵
};↵
```↵

- The `in` stack collects new items.↵
- When needing to pop or peek the oldest, items are transferred from `in` to `out`.↵

---↵

## 4. Augmented Stack for Aggregates (Min Stack Problem)↵

A simplified approach to track the **minimum** in a stack is to store each value along with the current minimum up to that point:↵

```cpp↵
struct AggStack {↵
    // Each element is stored as (value, current_min)↵
    stack<pair<int, int>> st;↵
    ↵
    // Push a new number; compute the new min.↵
    void push(int x) {↵
        int cur = st.empty() ? x : min(st.top().second, x);↵
        st.push({x, cur});↵
    }↵
    ↵
    // Pop the top element.↵
    void pop() {↵
        st.pop();↵
    }↵
    ↵
    // Return the current minimum.↵
    int agg() const {↵
        return st.top().second;↵
    }↵
};↵
```↵

Each push/pop here works in O(1).↵

---↵

## 5. Aggregated Queue & Sliding-Window Minimum↵

We now combine two augmented stacks to create an aggregated queue that can give the current minimum in O(1):↵

```cpp↵
struct AggQueue {↵
    AggStack in, out;↵
    ↵
    // Push a new number into the queue.↵
    void push(int x) {↵
        in.push(x);↵
    }↵
    ↵
    // Pop the oldest number.↵
    void pop() {↵
        if (out.st.empty()) {↵
            while (!in.st.empty()) {↵
                int v = in.st.top().first;↵
                in.pop();↵
                out.push(v);↵
            }↵
        }↵
        out.pop();↵
    }↵
    ↵
    // Query the current minimum.↵
    int query() const {↵
        if (in.st.empty()) return out.agg();↵
        if (out.st.empty()) return in.agg();↵
        return min(in.agg(), out.agg());↵
    }↵
};↵
```↵


```cpp↵
vector<int> slideMin(const vector<int>& a, int K) {↵
    int n = a.size();↵
    vector<int> ans;↵
    AggQueue mq;  // Our aggregated queue maintains the minimum↵
    ↵
    // Build the initial window of size K.↵
    for (int i = 0; i < K; i++) {↵
        mq.push(a[i]);↵
    }↵
    ans.push_back(mq.query());↵
    ↵
    // Slide the window: add a new element and remove the oldest.↵
    for (int i = K; i < n; i++) {↵
        mq.push(a[i]);   // add new element↵
        mq.pop();        // remove element that's left the window↵
        ans.push_back(mq.query());↵
    }↵
    ↵
    return ans;↵
}↵
```↵

*Note:* You can modify the AggStack and AggQueue to work with other aggregates (e.g., maximum, GCD) by replacing `min` with the appropriate function.↵

---↵

## Practice Problems↵

- [Sliding Window Sum](https://cses.fi/problemset/task/3220)  ↵
- [Sliding Window XOR](https://cses.fi/problemset/task/3426)  ↵
- [Sliding Window Distinct Values] (https://cses.fi/problemset/task/3222)↵
- [Sliding Window Minimum](https://cses.fi/problemset/task/3221)  ↵
- [Sliding Window OR](https://cses.fi/problemset/task/3405)↵
- [Sliding Window Median] (https://cses.fi/problemset/task/1076) (also requires ordered set / two priority queues or multisets)↵
- [Sliding Window Cost] (https://cses.fi/problemset/task/1077) (same idea as median)

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en4 Английский pasricha_dhruv 2025-05-30 12:45:34 0 (published)
en3 Английский pasricha_dhruv 2025-05-30 12:44:58 413 Tiny change: 'minimum** &mdash; and promp' -> 'minimum** and promp'
en2 Английский pasricha_dhruv 2025-05-30 12:37:14 128 Tiny change: 'nFor **Min**, **Max**, **B' -> 'nFor **Min / Max**, **B'
en1 Английский pasricha_dhruv 2025-05-30 12:33:18 6211 Initial revision (saved to drafts)