Array Technique

🪟 Sliding Window – Cửa sổ trượt

Sliding Window duy trì thông tin của một đoạn liên tiếp khi biên trái và biên phải thay đổi. Nhờ cập nhật phần thêm vào và loại bỏ phần rời khỏi cửa sổ, nhiều bài toán O(nk) hoặc O(n²) được giảm xuống O(n).

Fixed-sizeCửa sổ có độ dài k
Variable-sizeCo giãn theo điều kiện
O(n)Mỗi con trỏ chỉ tiến
DequeMax/min cửa sổ

🎯 Mục tiêu bài học

Cần hiểu

  • Sliding Window là gì và khác brute force thế nào.
  • Cửa sổ cố định và cửa sổ co giãn.
  • Cách cập nhật tổng/tần suất khi cửa sổ dịch chuyển.
  • Điều kiện đơn điệu để dùng cửa sổ co giãn.
  • Khi nào cần deque để duy trì max/min.

Dấu hiệu nhận biết

  • Đoạn con liên tiếp.
  • Mỗi đoạn có độ dài k.
  • Đoạn dài nhất/ngắn nhất thỏa điều kiện.
  • Tổng, số loại, tần suất trong cửa sổ.
  • Hai biên chỉ di chuyển sang phải.

1. Đặt vấn đề

Bài toán mở đầu 1 – cửa sổ cố định.
Cho mảng n số và số k. Hãy tìm tổng lớn nhất của một đoạn liên tiếp có đúng k phần tử.

1.1. Cách làm trực tiếp

Với mỗi vị trí bắt đầu, cộng lại k phần tử. Có khoảng n cửa sổ, mỗi cửa sổ mất O(k), tổng O(nk).

1.2. Quan sát khi dịch cửa sổ

Khi cửa sổ [L,R] dịch sang phải thành [L+1,R+1]:

  • Loại bỏ a[L].
  • Thêm a[R+1].
newSum = oldSum - a[L] + a[R+1]
Bài toán mở đầu 2 – cửa sổ co giãn.
Cho mảng số không âm và K. Tìm đoạn liên tiếp dài nhất có tổng không vượt quá K.

Ta mở rộng R. Nếu tổng vượt K, tăng L để thu nhỏ cửa sổ cho đến khi hợp lệ.

Bài toánBrute forceSliding Window
Tổng lớn nhất đoạn dài kO(nk)O(n)
Đoạn dài nhất có tổng ≤ K, số không âmO(n²)O(n)
Bản chất: không tính lại từ đầu; chỉ cập nhật phần tử vừa vào và phần tử vừa ra khỏi cửa sổ.

2. Các dạng Sliding Window

Cửa sổ cố định

Độ dài luôn bằng k.

Ví dụ: tổng/max/min mỗi đoạn k phần tử.

Cửa sổ co giãn

R mở rộng, L co lại để duy trì điều kiện.

Ví dụ: đoạn dài nhất có tổng ≤ K.

Cửa sổ + cấu trúc phụ

Dùng map, mảng đếm, deque.

Ví dụ: số ký tự khác nhau, max/min cửa sổ.

Điều kiện cốt lõi: cửa sổ co giãn cần tính đơn điệu. Nếu có số âm, tổng có thể thay đổi không dự đoán được và kỹ thuật có thể sai.

3. Mô phỏng cửa sổ cố định

4. Mô phỏng cửa sổ co giãn

5. Sliding Window với bảng tần suất

Bài toán mẫu: tìm độ dài đoạn dài nhất có không quá K giá trị khác nhau.

6. Sliding Window Maximum bằng deque

Để lấy max mỗi cửa sổ trong O(1) trung bình, deque lưu chỉ số theo thứ tự giá trị giảm dần.

Deque chỉ số

7. Phân tích thuật toán

DạngĐiều kiệnĐộ phức tạpLý do
Cửa sổ cố địnhĐộ dài kO(n)Mỗi bước thêm một, bỏ một.
Cửa sổ tổng ≤ KPhần tử không âmO(n)L và R chỉ tăng.
Không quá K loạiDuy trì tần suấtO(n)Mỗi phần tử vào/ra tối đa một lần.
Max/min mỗi cửa sổMonotonic dequeO(n)Mỗi chỉ số push/pop tối đa một lần.

8. Code mẫu

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, k;
    cin >> n >> k;

    vector<long long> a(n);
    for (long long &x : a) cin >> x;

    long long windowSum = 0;

    for (int i = 0; i < k; i++) {
        windowSum += a[i];
    }

    long long answer = windowSum;

    for (int right = k; right < n; right++) {
        windowSum += a[right];
        windowSum -= a[right - k];

        answer = max(answer, windowSum);
    }

    cout << answer;
}
int left = 0;
int best = 0;
long long sum = 0;

for (int right = 0; right < n; right++) {
    sum += a[right];

    while (left <= right && sum > k) {
        sum -= a[left];
        left++;
    }

    best = max(best, right - left + 1);
}
unordered_map<int,int> frequency;

int left = 0;
int distinct = 0;
int best = 0;

for (int right = 0; right < n; right++) {
    if (frequency[a[right]] == 0) {
        distinct++;
    }

    frequency[a[right]]++;

    while (distinct > k) {
        frequency[a[left]]--;

        if (frequency[a[left]] == 0) {
            distinct--;
        }

        left++;
    }

    best = max(best, right - left + 1);
}
deque<int> dq;

for (int i = 0; i < n; i++) {
    while (!dq.empty() && dq.front() <= i - k) {
        dq.pop_front();
    }

    while (!dq.empty() && a[dq.back()] <= a[i]) {
        dq.pop_back();
    }

    dq.push_back(i);

    if (i >= k - 1) {
        cout << a[dq.front()] << ' ';
    }
}
def max_sum_fixed_window(a, k):
    current = sum(a[:k])
    answer = current

    for right in range(k, len(a)):
        current += a[right]
        current -= a[right - k]
        answer = max(answer, current)

    return answer

9. Giải thích code

9.1. Vì sao cửa sổ cố định chỉ thêm một và bỏ một?

Khi dịch sang phải một vị trí, k−1 phần tử cũ vẫn nằm trong cửa sổ. Chỉ phần tử trái nhất rời đi và phần tử mới bên phải đi vào.

9.2. Vì sao while bên trong for vẫn O(n)?

Con trỏ left không quay lại. Trong toàn bộ chương trình, left tăng tối đa n lần; right cũng tăng n lần. Tổng là O(n).

9.3. Vì sao bài tổng ≤ K cần số không âm?

Nếu có số âm, khi tăng R tổng có thể giảm, và khi tăng L tổng có thể tăng. Khi đó quy tắc co cửa sổ không còn đúng.

9.4. Vì sao bảng tần suất cần giảm distinct khi count về 0?

Khi phần tử cuối cùng của một giá trị rời cửa sổ, giá trị đó không còn xuất hiện, nên số loại phải giảm.

9.5. Vì sao deque lưu chỉ số?

Cần biết phần tử nào đã ra khỏi cửa sổ. Chỉ số cho phép kiểm tra dq.front() <= i-k.

10. Lỗi thường gặp

  • Nhầm cửa sổ đóng [L,R] với độ dài R-L+1.
  • Quên trừ phần tử rời khỏi cửa sổ cố định.
  • Dùng cửa sổ tổng ≤ K khi mảng có số âm.
  • Co cửa sổ chỉ một lần bằng if thay vì while.
  • Cập nhật đáp án trước khi cửa sổ hợp lệ.
  • Quên giảm distinct khi tần suất về 0.
  • Sliding Window Maximum lưu giá trị thay vì chỉ số.
  • Điều kiện loại chỉ số cũ sai: đúng là dq.front() <= i-k.

11. Quiz và bài tập

Câu 1. Khi cửa sổ cố định dịch sang phải, cần làm gì?
Câu 2. Cửa sổ tổng ≤ K dạng chuẩn cần điều kiện gì?
Câu 3. Sliding Window Maximum dùng cấu trúc gì?

📘 Cơ bản

  1. Tổng lớn nhất đoạn dài k.
  2. Trung bình lớn nhất đoạn dài k.
  3. Đếm số chẵn trong mỗi cửa sổ.

📗 Trung bình

  1. Đoạn dài nhất tổng ≤ K.
  2. Đoạn không quá K loại.
  3. Chuỗi con dài nhất không lặp.

📙 Nâng cao

  1. Sliding Window Max/Min.
  2. Minimum Window Substring.
  3. Đếm đoạn có đúng K loại.

🐉 HSG

  1. Shortest subarray sum ≥ K có số âm.
  2. Sliding Window + multiset.
  3. DP optimization bằng deque.