슬라이딩 윈도우 | 부분 배열 최적화 기법 완벽 정리

슬라이딩 윈도우 | 부분 배열 최적화 기법 완벽 정리

이 글의 핵심

알고리즘 슬라이딩 윈도우는 고정·가변 길이의 연속 구간을 한 칸씩 밀며 O(n)으로 갱신하는 기법입니다. 이 글에서는 고정·가변 윈도우를 구분하고, 합·최댓값·문자열 조건 문제에서의 패턴과 투 포인터와의 차이를 예제로 정리합니다.

들어가며

연속 부분 배열이나 부분 문자열의 합·조건을 매번 처음부터 다시 계산하면 시간 초과가 나기 쉽습니다. 이 글에서는 윈도우를 한 칸씩 밀며 갱신하는 방식으로 복잡도를 줄이는 흐름을 단계적으로 익힐 수 있습니다.

”윈도우를 슬라이딩하며 최적화”

슬라이딩 윈도우는 연속된 부분 배열을 효율적으로 처리하는 기법입니다.


1. 고정 크기 윈도우

기본 패턴

def max_sum_subarray(arr, k):
    """
    크기 k인 부분 배열의 최대 합
    [1, 4, 2, 10, 23, 3, 1, 0, 20], k=4 → 39
    """
    if len(arr) < k:
        return None
    
    # 첫 윈도우 합
    window_sum = sum(arr[:k])
    max_sum = window_sum
    
    # 윈도우 이동
    for i in range(k, len(arr)):
        window_sum = window_sum - arr[i - k] + arr[i]
        max_sum = max(max_sum, window_sum)
    
    return max_sum

# 테스트
arr = [1, 4, 2, 10, 23, 3, 1, 0, 20]
print(max_sum_subarray(arr, 4))  # 39 (10+23+3+1)

2. 가변 크기 윈도우

최소 길이 부분 배열

def min_subarray_len(arr, target):
    """
    합이 target 이상인 최소 길이 부분 배열
    [2,3,1,2,4,3], target=7 → 2 ([4,3])
    """
    left = 0
    current_sum = 0
    min_len = float('inf')
    
    for right in range(len(arr)):
        current_sum += arr[right]
        
        # 조건 만족 시 윈도우 축소
        while current_sum >= target:
            min_len = min(min_len, right - left + 1)
            current_sum -= arr[left]
            left += 1
    
    return min_len if min_len != float('inf') else 0

# 테스트
arr = [2, 3, 1, 2, 4, 3]
print(min_subarray_len(arr, 7))  # 2

고정 크기 vs 가변 크기 윈도우

구분고정 크기가변 크기
윈도우 길이k 등으로 항상 동일조건(합, 중복, 카운트)에 따라 늘었다 줄었다
전형적 루프right만 전진하고, right - left + 1 == k일 때 한 칸씩 left도 전진right확장하고, 조건 깨질 때까지 left축소
대표 문제크기 k인 부분 배열 합·최댓값, 고정 길이 애너그램최소 길이 부분합, 중복 없는 최장 부분 문자열, 최소 윈도우 부분 문자열
투 포인터와left = right - k + 1로도 표현 가능아래 “확장·축소 패턴”과 동일한 두 포인터 사고

둘 다 left, right가 앞으로만 가는 “같은 방향 두 포인터”이며, 차이는 길이가 고정인지입니다.


3. 실전 문제

문제 1: 최대 연속 1의 개수

def longest_ones(arr, k):
    """
    최대 k개의 0을 1로 바꿀 때 최대 연속 1의 개수
    [1,1,1,0,0,0,1,1,1,1,0], k=2 → 6
    """
    left = 0
    zero_count = 0
    max_len = 0
    
    for right in range(len(arr)):
        if arr[right] == 0:
            zero_count += 1
        
        # 0이 k개 초과 시 윈도우 축소
        while zero_count > k:
            if arr[left] == 0:
                zero_count -= 1
            left += 1
        
        max_len = max(max_len, right - left + 1)
    
    return max_len

# 테스트
arr = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0]
print(longest_ones(arr, 2))  # 6

문제 2: 최장 부분 문자열

def length_of_longest_substring(s):
    """
    중복 문자 없는 최장 부분 문자열
    "abcabcbb" → 3 ("abc")
    """
    char_set = set()
    left = 0
    max_len = 0
    
    for right in range(len(s)):
        # 중복 제거
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        
        char_set.add(s[right])
        max_len = max(max_len, right - left + 1)
    
    return max_len

# 테스트
print(length_of_longest_substring("abcabcbb"))  # 3
print(length_of_longest_substring("bbbbb"))     # 1
print(length_of_longest_substring("pwwkew"))    # 3

문제 3: 애너그램 찾기

from collections import Counter

def find_anagrams(s, p):
    """
    문자열 s에서 p의 애너그램 시작 인덱스
    s="cbaebabacd", p="abc" → [0, 6]
    """
    result = []
    p_count = Counter(p)
    window_count = Counter()
    
    left = 0
    
    for right in range(len(s)):
        window_count[s[right]] += 1
        
        # 윈도우 크기 유지
        if right - left + 1 > len(p):
            window_count[s[left]] -= 1
            if window_count[s[left]] == 0:
                del window_count[s[left]]
            left += 1
        
        # 애너그램 확인
        if window_count == p_count:
            result.append(left)
    
    return result

# 테스트
print(find_anagrams("cbaebabacd", "abc"))
# [0, 6]

대표 문제 (LeetCode): 최장 부분 문자열 · 최소 윈도우 · 고정 합

아래 세 가지는 슬라이딩 윈도우를 공부할 때 거의 필수로 거론되는 유형입니다. (이 글 앞쪽 예제와 연결해 읽으면 좋습니다.)

문제유형핵심
3. Longest Substring Without Repeating Characters가변, 조건: 중복 없음right 추가 → 중복 시 left를 당겨서 중복 제거
76. Minimum Window Substring가변, need/coverright로 need 충족까지 확장 → 최소가 되도록 left 축소
643. Maximum Average Subarray I (또는 합 최대)고정 길이 k한 칸 밀 때 sum -= out, sum += in, O(n)

Minimum Window Substring 스케치 (Python)

from collections import Counter

def min_window(s: str, t: str) -> str:
    need = Counter(t)
    missing = len(t)
    left = 0
    best = (float("inf"), None, None)  # 길이, 시작, 끝

    for right, ch in enumerate(s):
        if need[ch] > 0:
            missing -= 1
        need[ch] -= 1

        while missing == 0:
            if right - left + 1 < best[0]:
                best = (right - left + 1, left, right)
            need[s[left]] += 1
            if need[s[left]] > 0:
                missing += 1
            left += 1

    return "" if best[1] is None else s[best[1] : best[2] + 1]

(실제 제출 시에는 need/window를 나누어 쓰는 방식도 많습니다. 위는 “missing 카운트로 조건 충족”을 강조한 최소 예입니다.)


시간 복잡도 분석

  • 브루트 포스
    모든 연속 구간 [left, right]를 이중 루프로 보면 O(n²)~O(n³)입니다.
  • 슬라이딩 윈도우
    leftright가 각각 최대 n번만 앞으로 이동하면 전체 O(n)입니다. (한 번의 right 전진에 left가 여러 번 움직여도, left가 돌아가지 않으므로 합쳐서 선형입니다.)
  • 맵/카운터
    문자·빈도를 유지하면 삽입·갱신이 문제당 O(1)~O(σ) (알파벳 크기 σ)이고, 보통 O(n) 또는 O(n·σ)로 표현합니다.
  • 고정 크기 k
    초기 합 O(k) + 슬라이드 O(n−k) → O(n).

윈도우 확장·축소 조건 패턴

  1. 확장 (right++)
    항상 한 칸 넓혀서 현재 구간에 원소를 포함합니다. 고정 윈도우는 right만 순차 증가하고, 가변은 “조건을 만족시키기 위해” 넓힙니다.
  2. 축소 (left++)
    • 고정 크기: right - left + 1 > k이면 왼쪽을 한 칸 당겨 길이 k 유지.
    • 가변 — 유효하지 않을 때: 예) 중복 문자, 0이 너무 많음, need 미충족이 아닌 “최소 길이” 찾기 전 단계 등.
    • 가변 — 유효한데 더 짧게 줄일 수 있을 때: Minimum Window에서 need 충족 후 left를 당겨 최소 길이 갱신.
  3. 불변식(invariant)
    “윈도우가 만족해야 할 조건”을 한 문장으로 적어 두면, 언제 while로 left를 당길지가 명확해집니다. (예: “중복 문자가 없을 때까지 left 이동”.)

실전 팁

슬라이딩 윈도우 패턴

# 패턴 1: 고정 크기
left = 0
for right in range(len(arr)):
    add(arr[right])
    
    if right - left + 1 == k:
        process_window()
        remove(arr[left])
        left += 1

# 패턴 2: 가변 크기
left = 0
for right in range(len(arr)):
    add(arr[right])
    
    while not is_valid():
        remove(arr[left])
        left += 1
    
    update_result()

정리

핵심 요약

  1. 슬라이딩 윈도우: 연속 부분 배열 O(n) 처리
  2. 고정 크기: 윈도우 크기 일정
  3. 가변 크기: 조건에 따라 확장/축소
  4. 최적화: O(n²) → O(n)

추천 문제

백준:

프로그래머스:

LeetCode:


관련 글