Майстерність у техніці ковзного вікна: посібник з оптимізованого розв’язання задач

Метод "ковзного вікна" (Sliding Window) є одним із найефективніших підходів до вирішення задач, що включають суміжні підмасиви (subarrays) або підстроки (substrings). Цей метод значно знижує часову складність у порівнянні з методами грубої сили (brute-force), що робить його незамінним інструментом у арсеналі кожного програміста.

У цій статті ми розглянемо задачі з "ковзним вікном", розділивши їх на два типи: фіксоване вікно (Fixed Size Window) та змінне вікно (Variable Size Window).

pic

A. Фіксоване вікно

Фіксоване ковзне вікно передбачає обробку підмасивів або підстрок певної довжини у вхідній послідовності. Це особливо корисно для задач, де потрібно проаналізувати кожен суміжний сегмент розміру k.

1. Максимальна/мінімальна сума підмасиву розміру k

Задача 1: Дано масив, знайти максимальну (або мінімальну) суму підмасиву розміру k.

Підхід до вирішення:

  • Використовуйте метод ковзного вікна для підтримки суми поточного підмасиву розміру k.
  • Зрушуйте вікно по масиву, ефективно оновлюючи суму шляхом видалення вихідного елемента та додавання нового.

Метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили  
def max_subarray_sum_brute_force(arr, k):  
 if len(arr) < k:  
 return None # Недостатньо елементів для підмасиву розміру k  

 max_sum = float('-inf') # Ініціалізуємо max_sum як мінус нескінченність  

 for i in range(len(arr) - k + 1): # Цикл по всіх можливих підмасивах розміру k  
 current_sum = sum(arr[i:i + k]) # Обчислюємо суму підмасиву від i до i+k-1  
 max_sum = max(max_sum, current_sum) # Оновлюємо max_sum, якщо current_sum більша  

 return max_sum  
# Приклад використання  
arr = [2, 1, 5, 1, 3, 2]  
k = 3  
print(max_subarray_sum_brute_force(arr, k)) # Вивід: 9
# Метод "ковзного вікна"  
def maximum_subarray_sum(arry, k):  
 if len(arry) < k:  
 return None # Недостатньо елементів для підмасиву розміру k  
 i, j = 0, 0  
 sum_1 = 0  
 max_1 = float('-inf') # Ініціалізуємо max_sum як мінус нескінченність  
 while j < len(arry):  
 sum_1 += arry[j] # Додаємо поточний елемент до вікна  
 if j - i + 1 == k: # Коли розмір вікна досягає k  
 max_1 = max(max_1, sum_1) # Оновлюємо max_sum  
 sum_1 -= arry[i] # Видаляємо перший елемент із вікна  
 i += 1 # Зсуваємо вікно  
 j += 1 # Розширюємо вікно  
 return max_1  
# Приклад використання  
arr = [2, 1, 5, 1, 3, 2]  
k = 3  
print(maximum_subarray_sum(arr, k)) # Вивід: 9 (підмасив [5, 1, 3])

Аналіз складності: метод грубої сили проти "ковзного вікна"

Метод грубої сили

Аналіз часової складності:

  • Зовнішній цикл виконується O(N) разів.
  • Кожна ітерація обчислює суму k елементів → O(k).
  • Загальна часова складність: O(N×k). Це неефективно для великих N.

Складність за пам'яттю:

  • O(1) (постійна пам'ять), оскільки використовуються лише кілька змінних.

Метод "ковзного вікна"

Аналіз часової складності:

  • Спочатку обчислюється сума перших k елементів → O(k).
  • Потім ітеруємо через решту масиву, оновлюючи суму за постійний час → O(N−k).
  • Загальна часова складність: O(N). Це значно ефективніше, ніж метод грубої сили.

Складність за пам'яттю:

  • O(1) (постійна пам'ять), оскільки використовуються лише кілька додаткових змінних.

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|----------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N×k) | O(1) | Повільний для великих N |
| Метод "ковзного вікна" | O(N) | O(1) | Швидкий та ефективний |

2. Перший від’ємний елемент у кожному вікні розміру k

Задача 2: Дано масив цілих чисел, для кожного підмасиву розміру k знайти перший від’ємний елемент.
Якщо такого числа немає, поверніть 0.

Підхід до вирішення:

  • Використовуйте чергу (queue) для зберігання індексів від’ємних чисел.
  • Під час зсуву вікна оновлюйте чергу та ефективно отримуйте перший від’ємний елемент.

Метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

#Метод грубої сили  
def first_negative_value_brute_force(arr, k):  
 n = len(arr)  
 first_neg = []  

 for i in range(n - k + 1): # Ітерація по всіх можливих підмасивах розміру k  
 found = False  
 for j in range(i, i + k): # Перевірка елементів у поточному вікні  
 if arr[j] < 0:  
 first_neg.append(arr[j]) # Додати перше від’ємне число  
 found = True  
 break # Зупинити пошук після знаходження першого від’ємного  
 if not found:  
 first_neg.append(0) # Якщо від’ємного числа не знайдено, додати 0  

 return first_neg  

# Приклад використання  
arr = [2, -1, 3, -4, 5, -6, 7, -8, 9]  
k = 3  
print(first_negative_value_brute_force(arr, k))   
# Вивід: [-1, -1, -4, -4, -6, -6, -8]
#Метод "ковзного вікна"  
def first_nagative_value(arry,k):  
 n=len(arry)  
 current_neg=[]  
 first_neg=[]  
 i,j=0,0  
 while j

## Максимум для всіх підмасивів розміру k

**Задача 4:** Дано масив, знайти максимальне значення у кожному підмасиві розміру `k`.

**Підхід до вирішення:**

- Використовуйте дек (deque, двостороння черга) для збереження індексів елементів у порядку спадання.
- Переконайтеся, що максимальний елемент у поточному вікні завжди знаходиться на початку дека.

**Метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)**

Метод грубої сили

def maxinsubarraysbruteforce(arr, k):
n = len(arr)
result = []

for i in range(n - k + 1): # Ітерація по всіх можливих підмасивах розміру k
maxvalue = arr[i] # Припустимо, що перший елемент є максимальним
for j in range(i, i + k): # Перевіряємо всі елементи в межах вікна
if arr[j] > max
value:
maxvalue = arr[j] # Оновлюємо maxvalue, якщо знайдено більший елемент
result.append(max_value) # Додаємо максимум для поточного вікна

return result

Приклад використання

arr = [2, 6, 7, 1, 9, 3, 5, 8]
k = int(input("Введіть розмір підмасиву: "))

result = maxinsubarraysbruteforce(arr, k)
print("Максимум у кожному підмасиві:", result)
```

# Метод "ковзного вікна"  
def max_in_subarrays(arr, k):  
 n = len(arr)  
 q = [] # Список для збереження індексів  
 result = []  

 for i in range(n):  
 # Видаляємо елементи, які не входять у поточне вікно  
 while q and q[0] < i - k + 1:  
 q.pop(0)  

 # Видаляємо всі елементи, менші за поточний елемент  
 while q and arr[q[-1]] < arr[i]:  
 q.pop()  

 # Додаємо індекс поточного елемента до списку  
 q.append(i)  

 # Додаємо максимум вікна до результату  
 if i >= k - 1:  
 result.append(arr[q[0]])  

 return result  

# Приклад використання  
arr = [2, 6, 7, 1, 9, 3, 5, 8]  
k = int(input("Введіть розмір підмасиву: "))  

result = max_in_subarrays(arr, k)  
print("Максимум у кожному підмасиві:", result)

Порівняння часової та просторової складності

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N×k) | O(1) | Повільний для великих N |
| Оптимізований метод "ковзного вікна" | O(N) | O(k) | Швидкий та ефективний |

Максимум із мінімумів кожного підмасиву розміру k

Задача 5: Дано масив, знайти максимальне значення серед мінімальних значень усіх підмасивів розміру k.

Підхід до вирішення:

  • Використовуйте дек (deque) для відстеження мінімальних елементів у поточному вікні.
  • Зсувайте вікно, ефективно оновлюючи дек.

Метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили  
def max_of_minimums_brute_force(arr, k):  
 n = len(arr)  
 if k > n:  
 return None # Крайній випадок: розмір підмасиву більший за розмір масиву  

 max_min = float('-inf') # Для збереження максимального серед мінімальних значень  

 for i in range(n - k + 1): # Ітерація по всіх можливих підмасивах розміру k  
 current_min = float('inf') # Ініціалізація мінімуму для поточного вікна  
 for j in range(i, i + k): # Ітерація по підмасиву  
 current_min = min(current_min, arr[j]) # Оновлення мінімуму в межах вікна  
 max_min = max(max_min, current_min) # Оновлення максимального серед мінімумів  

 return max_min  

# Приклад використання  
arr = [10, 20, 10, 50, 10, 70, 30]  
k = 3  
result = max_of_minimums_brute_force(arr, k)  
print("Максимум серед мінімумів:", result)
# Метод "ковзного вікна"  
def max_of_minimums_sliding_window_list(arr, k):  
 n = len(arr)  
 if k > n:  
 return None # Крайній випадок: розмір підмасиву більший за розмір масиву  

 window = [] # Список для збереження індексів мінімальних елементів  
 max_min = float('-inf') # Для збереження максимального серед мінімальних значень  

 for i in range(n):  
 # Видалення елементів, які виходять за межі поточного вікна  
 if window and window[0] < i - k + 1:  
 window.pop(0)  

 # Видалення елементів з кінця, які не є корисними (більші елементи)  
 while window and arr[window[-1]] > arr[i]:  
 window.pop()  

 # Додавання індексу поточного елемента до списку  
 window.append(i)  

 # Коли опрацьовано принаймні `k` елементів, враховуємо мінімум  
 if i >= k - 1:  
 max_min = max(max_min, arr[window[0]]) # arr[window[0]] — це мінімум у вікні  

 return max_min  

# Приклад використання  
arr = [10, 20, 10, 50, 10, 70, 30]  
k = 3  
result = max_of_minimums_sliding_window_list(arr, k)  
print("Максимум серед мінімумів:", result)

Часова та просторова складність

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N×k) | O(1) | Повільний для великих N |
| Метод "ковзного вікна" | O(N) | O(k) | Швидкий та ефективний |

Змінне ковзне вікно

Змінне ковзне вікно корисне, коли розмір вікна не є заздалегідь визначеним, і ми маємо динамічно розширювати або звужувати вікно залежно від умов.

Найбільший/найменший підмасив із сумою k

Задача 6: Дано масив і суму k, знайти найбільший (або найменший) підмасив, чия сума дорівнює k.

Підхід до вирішення:

  • Використовуйте два вказівники (left і right) для динамічного розширення та скорочення вікна.
  • Ефективно оновлюйте суму в процесі зміни вікна.

Метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили  
def largest_subarray_with_sum_k_brute_force(arr, k):  
 n = len(arr)  
 max_length = 0  

 # Перевіряємо всі можливі підмасиви  
 for i in range(n):  
 current_sum = 0  
 for j in range(i, n):  
 current_sum += arr[j] # Сума підмасиву від i до j  
 if current_sum == k:  
 max_length = max(max_length, j - i + 1)  

 return max_length  

# Приклад використання  
arr = [2, 6, 7, 1, 9, 3, 5, 8]  
k = int(input("Введіть суму k: "))  

result = largest_subarray_with_sum_k_brute_force(arr, k)  
print("Довжина найбільшого підмасиву із сумою k:", result)
# Метод "ковзного вікна"  
# Функція для знаходження найбільшого або найменшого підмасиву із сумою k  
def largest_subarray_with_sum_k(arr, k):  
 sum_map = {}  
 max_length = 0  
 current_sum = 0  

 for i in range(len(arr)):  
 current_sum += arr[i]  

 # Якщо поточна сума дорівнює k  
 if current_sum == k:  
 max_length = i + 1  

 # Якщо current_sum - k існує в мапі, оновлюємо max_length  
 if current_sum - k in sum_map:  
 max_length = max(max_length, i - sum_map[current_sum - k])  

 # Зберігаємо перше входження поточної суми  
 if current_sum not in sum_map:  
 sum_map[current_sum] = i  

 return max_length  

# Приклад використання  
arr = [2, 6, 7, 1, 9, 3, 5, 8]  
k = int(input("Введіть суму k: "))  

result = largest_subarray_with_sum_k(arr, k)  
print("Довжина найбільшого підмасиву із сумою k:", result)

Часова та просторова складність

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N²) | O(1) | Дуже повільний для великих N |
| Оптимізований метод | O(N) | O(N) | Швидкий та ефективний |

Найбільший підрядок із k унікальними символами

Задача 7: Дано рядок, знайти довжину найбільшого підрядка, який містить рівно k унікальних символів.

Підхід до вирішення:

  • Використовуйте хешмапу (hashmap) для відстеження частот символів.
  • Розширюйте вікно, підтримуючи рівно k унікальних символів.

Аналіз складності: метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили  
def longest_substring_with_k_distinct_brute_force(s, k):  
 n = len(s)  
 max_length = 0  

 # Перевіряємо всі підрядки  
 for i in range(n):  
 distinct_chars = set()  
 for j in range(i, n):  
 distinct_chars.add(s[j])  

 # Перевіряємо, чи має підрядок не більше ніж k унікальних символів  
 if len(distinct_chars) > k:  
 break # Зупиняємо розширення цього підрядка  

 max_length = max(max_length, j - i + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
k = int(input("Введіть кількість унікальних символів k: "))  

result = longest_substring_with_k_distinct_brute_force(s, k)  
print("Найдовший підрядок із не більше ніж k унікальними символами:", result)
# Метод "ковзного вікна"  

def longest_substring_with_k_distinct(s, k):  
 n = len(s)  
 char_map = {}  
 max_length = 0  
 left = 0  

 for right in range(n):  
 char_map[s[right]] = char_map.get(s[right], 0) + 1  

 while len(char_map) > k:  
 char_map[s[left]] -= 1  
 if char_map[s[left]] == 0:  
 del char_map[s[left]]  
 left += 1  

 max_length = max(max_length, right - left + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
k = int(input("Введіть кількість унікальних символів k: "))  

result = longest_substring_with_k_distinct(s, k)  
print(result)

Часова та просторова складність

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N²) | O(K) | Повільний для великих N |
| Метод "ковзного вікна" | O(N) | O(K) | Значно швидший |

Довжина найбільшого підрядка без повторюваних символів

Задача 8: Дано рядок, знайти довжину найбільшого підрядка без повторюваних символів.

Підхід до вирішення:

  • Використовуйте множину (set) для відстеження унікальних символів.
  • Розширюйте вікно, забезпечуючи відсутність дублікатів.

Аналіз складності: метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили   
def longest_substring_without_repeating_brute_force(s):  
 n = len(s)  
 max_length = 0  

 # Перевіряємо всі підрядки  
 for i in range(n):  
 seen_chars = set()  
 for j in range(i, n):  
 if s[j] in seen_chars: # Якщо символ повторюється, зупиняємо  
 break  
 seen_chars.add(s[j])  
 max_length = max(max_length, j - i + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
result = longest_substring_without_repeating_brute_force(s)  
print("Найдовший підрядок без повторюваних символів:", result)
# Метод "ковзного вікна"  
def longest_substring_without_repeating(s):  
 n = len(s)  
 char_map = {}  
 max_length = 0  
 left = 0  

 for right in range(n):  
 if s[right] in char_map:  
 left = max(left, char_map[s[right]] + 1)  

 char_map[s[right]] = right  
 max_length = max(max_length, right - left + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
result = longest_substring_without_repeating(s)  
print(result)

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N²) | O(N) | Повільний для великих N |
| Метод "ковзного вікна" | O(N) | O(N) | Значно швидший |

Вибір іграшок (або підмасив із певними умовами)

Задача 9: Дано масив, знайти найбільший підмасив, який можна вибрати, щоб він задовольняв певні умови (наприклад, вибір іграшок у межах обмеження ваги).

Підхід до вирішення:

  • Подібно до задачі з k унікальними символами, використовуйте карту частот (frequency map).
  • Розширюйте та скорочуйте вікно, щоб задовольнити задану умову.

Аналіз складності: метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

# Метод грубої сили  
def longest_substring_without_repeating_brute_force(s):  
 n = len(s)  
 max_length = 0  

 # Перевіряємо всі підрядки  
 for i in range(n):  
 seen_chars = set()  
 for j in range(i, n):  
 if s[j] in seen_chars: # Якщо символ повторюється, зупиняємо  
 break  
 seen_chars.add(s[j])  
 max_length = max(max_length, j - i + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
result = longest_substring_without_repeating_brute_force(s)  
print("Найдовший підрядок без повторюваних символів:", result)
# Метод "ковзного вікна"  
def longest_substring_without_repeating(s):  
 n = len(s)  
 char_map = {}  
 max_length = 0  
 left = 0  

 for right in range(n):  
 if s[right] in char_map:  
 left = max(left, char_map[s[right]] + 1)  

 char_map[s[right]] = right  
 max_length = max(max_length, right - left + 1)  

 return max_length  

# Приклад використання  
s = input("Введіть рядок: ")  
result = longest_substring_without_repeating(s)  
print(result)

Часова та просторова складність

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N²) | O(N) | Повільний для великих N |
| Метод "ковзного вікна" | O(N) | O(N) | Значно швидший |

Мінімальний підрядок

Задача 10: Дано два рядки s і t. Знайти мінімальний підрядок у s, який містить усі символи з t.

Підхід до вирішення:

  • Використовуйте карту частот (frequency map) для відстеження необхідних символів.
  • Розширюйте вікно, доки всі символи не будуть включені, потім скорочуйте його, щоб знайти найменше можливе валідне вікно.

Аналіз складності: метод грубої сили (Brute Force) проти методу "ковзного вікна" (Sliding Window)

from collections import Counter  

def min_window_brute_force(s, t):  
 if not s or not t:  
 return ""  

 t_counter = Counter(t)  
 min_length = float("inf")  
 ans = ""  

 # Генеруємо всі можливі підрядки рядка s  
 for i in range(len(s)):  
 for j in range(i + 1, len(s) + 1):  
 # Отримуємо поточний підрядок  
 current_substring = s[i:j]  

 # Якщо поточний підрядок містить усі символи з t  
 if all(current_substring.count(char) >= t_counter[char] for char in t_counter):  
 # Оновлюємо відповідь, якщо знайдено менший валідний підрядок  
 if len(current_substring) < min_length:  
 min_length = len(current_substring)  
 ans = current_substring  

 return ans  

# Приклад використання  
s = input("Введіть рядок s: ")  
t = input("Введіть рядок t: ")  
result = min_window_brute_force(s, t)  
print(result)
from collections import Counter  

def min_window(s, t):  
 if not s or not t:  
 return ""  

 # Карта частот символів у t  
 t_counter = Counter(t)  
 current_counter = Counter()  

 required = len(t_counter)  
 formed = 0  

 left = 0  
 right = 0  
 min_length = float("inf")  
 ans = (0, 0)  

 # Розширюємо вікно правим вказівником  
 while right < len(s):  
 current_counter[s[right]] += 1  

 # Якщо поточне вікно містить усі символи з t  
 if s[right] in t_counter and current_counter[s[right]] == t_counter[s[right]]:  
 formed += 1  

 # Скорочуємо вікно зліва, щоб знайти найменше валідне вікно  
 while left <= right and formed == required:  
 if right - left + 1 < min_length:  
 min_length = right - left + 1  
 ans = (left, right)  

 # Скорочуємо вікно зліва  
 current_counter[s[left]] -= 1  
 if s[left] in t_counter and current_counter[s[left]] < t_counter[s[left]]:  
 formed -= 1  
 left += 1  

 right += 1  

 # Якщо знайдено валідне вікно, повертаємо підрядок, інакше повертаємо порожній рядок  
 return s[ans[0]:ans[1] + 1] if min_length != float("inf") else ""  

# Приклад використання  
s = input("Введіть рядок s: ")  
t = input("Введіть рядок t: ")  
result = min_window(s, t)  
print(result)

Часова та просторова складність

| Підхід | Часова складність | Складність за пам’яттю | Ефективність |
|------------------------|-----------------------|----------------------------|------------------------------|
| Метод грубої сили | O(N³) | O(N) | Дуже неефективний для великих рядків |
| Оптимізований метод | O(N) | O(N) | Значно ефективніший |

Перекладено з: Mastering the Sliding Window Technique: A Guide to Optimized Problem-Solving

Leave a Reply

Your email address will not be published. Required fields are marked *