Khái niệm
Cửa sổ trượt (sliding window) là kỹ thuật duy trì một dãy con liên tiếp (cửa sổ) và di chuyển nó qua mảng từ trái sang phải. Thay vì tính lại từ đầu mỗi khi cửa sổ di chuyển, chỉ cập nhật sự thay đổi ở biên — xóa phần tử bên trái (out), thêm phần tử bên phải (in).
Khi nào dùng:
- Tìm dãy con liên tiếp thoả điều kiện (tổng, trung bình, max/min, số phần tử phân biệt…)
- Cửa sổ kích thước cố định (fixed window) hoặc co giãn (variable window)
Sơ đồ nguyên lý
graph LR;
A["[1, 3, 5, 2, 8, 9, 3]"] --> B["Cửa sổ: 1,3,5\ntổng=9"];
B --> C["Trượt 1:\n-1, +2 → 3,5,2\ntổng=10"];
C --> D["Trượt 2:\n-3, +8 → 5,2,8\ntổng=15"];
D --> E["..."];
Cửa sổ kích thước cố định
def max_tong_khi_cua_so_co_dinh(arr, k):
"""Tìm tổng lớn nhất của dãy con liên tiếp k phần tử."""
n = len(arr)
if n < k:
return None
# Tính tổng cửa sổ đầu tiên
tong = sum(arr[:k])
max_tong = tong
# Trượt cửa sổ: bỏ phần tử trái, thêm phần tử phải
for i in range(k, n):
tong += arr[i] - arr[i - k]
max_tong = max(max_tong, tong)
return max_tong
arr = [2, 1, 5, 1, 3, 2]
print(max_tong_khi_cua_so_co_dinh(arr, 3)) # 9 (dãy [5, 1, 3])
print(max_tong_khi_cua_so_co_dinh(arr, 2)) # 6 (dãy [5, 1] hay [3,2]? → [5,1] cho 6)
Cửa sổ co giãn (variable window)
def day_con_ngan_nhat_co_tong_du(arr, target):
"""
Tìm độ dài nhỏ nhất của dãy con liên tiếp có tổng >= target.
Cửa sổ mở rộng từ phải, thu nhỏ từ trái khi đủ điều kiện.
"""
left = 0
tong = 0
min_len = float('inf')
for right in range(len(arr)):
tong += arr[right]
while tong >= target:
min_len = min(min_len, right - left + 1)
tong -= arr[left]
left += 1
return min_len if min_len != float('inf') else 0
print(day_con_ngan_nhat_co_tong_du([2, 3, 1, 2, 4, 3], 7)) # 2 ([4,3])
print(day_con_ngan_nhat_co_tong_du([1, 4, 4], 4)) # 1 ([4])
print(day_con_ngan_nhat_co_tong_du([1, 1, 1, 1, 1], 11)) # 0 (không có)
Bài tập luyện tập
Dễ — Trung bình cộng của cửa sổ k phần tử
def trung_binh_cua_so(arr, k):
"""Trả về danh sách trung bình liên tiếp của từng cửa sổ k phần tử."""
n = len(arr)
if n < k:
return []
tong = sum(arr[:k])
result = [tong / k]
for i in range(k, n):
tong += arr[i] - arr[i - k]
result.append(tong / k)
return result
arr = [1, 3, 2, 6, -1, 4, 1, 8, 2]
print(trung_binh_cua_so(arr, 5))
# [2.2, 2.8, 2.4, 3.6, 2.8]
Trung bình — Chuỗi con dài nhất không trùng ký tự
def chuoi_con_khong_trung_dai_nhat(s):
"""
Tìm độ dài chuỗi con liên tiếp không có ký tự lặp.
Dùng dict để biết ký tự xuất hiện lần cuối ở đâu.
"""
last_seen = {} # ký tự → vị trí xuất hiện gần nhất
left = 0
max_len = 0
for right, ch in enumerate(s):
if ch in last_seen and last_seen[ch] >= left:
left = last_seen[ch] + 1 # Thu hẹp cửa sổ
last_seen[ch] = right
max_len = max(max_len, right - left + 1)
return max_len
print(chuoi_con_khong_trung_dai_nhat("abcabcbb")) # 3 ("abc")
print(chuoi_con_khong_trung_dai_nhat("bbbbb")) # 1 ("b")
print(chuoi_con_khong_trung_dai_nhat("pwwkew")) # 3 ("wke")
Khó — Cửa sổ nhỏ nhất chứa tất cả ký tự cho trước
Tìm chuỗi con ngắn nhất của s chứa tất cả các ký tự của t (kể cả trùng lặp).
from collections import Counter
def cua_so_nho_nhat_chua_t(s, t):
"""
Tìm chuỗi con ngắn nhất của s chứa đủ tất cả ký tự của t.
"""
if not t or not s:
return ""
need = Counter(t) # Số lần cần có của mỗi ký tự
missing = len(t) # Số ký tự còn thiếu
best = ""
left = 0
for right, ch in enumerate(s):
if need[ch] > 0:
missing -= 1 # Thoả mãn thêm 1 ký tự cần
need[ch] -= 1
while missing == 0: # Cửa sổ đang đủ điều kiện → thu nhỏ từ trái
window = s[left:right + 1]
if not best or len(window) < len(best):
best = window
need[s[left]] += 1
if need[s[left]] > 0:
missing += 1
left += 1
return best
print(cua_so_nho_nhat_chua_t("ADOBECODEBANC", "ABC")) # "BANC"
print(cua_so_nho_nhat_chua_t("a", "a")) # "a"
print(cua_so_nho_nhat_chua_t("a", "aa")) # "" (không đủ)
Kết luận
Cửa sổ trượt là công cụ đặc trưng cho các bài toán dãy con liên tiếp. Nhận ra cấu trúc: “có thể biểu diễn câu trả lời dưới dạng cửa sổ [left..right] không? Khi right tăng, điều kiện có thể duy trì hiệu quả không?” — nếu có, sliding window là lựa chọn tốt. Tiếp theo hãy học về Tổng tiền tố để giải quyết các truy vấn tổng hiệu quả.
Bình luận