그리디 알고리즘 | "매 순간 최선" 탐욕 알고리즘 완벽 정리

그리디 알고리즘 | "매 순간 최선" 탐욕 알고리즘 완벽 정리

이 글의 핵심

그리디 알고리즘은 매 단계에서 지역 최선을 고르는 전략으로, 조건이 맞으면 효율적으로 최적해를 얻을 수 있습니다. 이 글에서는 적용 조건, 대표 문제, 증명 없이 쓸 때의 위험, 시간·공간 복잡도 관점과 코딩 테스트 팁을 다룹니다.

들어가며

탐욕법은 구현은 쉬워 보여도 최적성이 맞는지 증명하지 않으면 잘못된 답을 낼 수 있습니다. 이 글에서는 전형적인 그리디 패턴을 익히고, 언제 DP나 다른 전략으로 넘어가야 하는지 감을 잡을 수 있습니다.

”매 순간 최선의 선택이 최적해?”

그리디 알고리즘은 매 순간 최선의 선택을 합니다. 항상 최적해를 보장하지는 않지만, 증명 가능한 경우 매우 효율적입니다.


1. 그리디 알고리즘이란?

개념

그리디(탐욕) 알고리즘은 현재 단계에서 보이는 지역 최선만 고릅니다. 아래는 큰 단위 동전부터 최대한 많이 쓰는 거스름돈 예시입니다. 단, 동전 액면가가 임의이면 그리디가 최소 개수를 보장하지 않을 수 있으므로, 문제에서 그리디가 성립하는지(교환·정렬 조건 등)를 확인해야 합니다.

# 예: 거스름돈 (500, 100, 50, 10원)
# 1260원 거슬러주기

coins = [500, 100, 50, 10]
change = 1260
count = 0

for coin in coins:
    count += change // coin  # 최대한 많이
    change %= coin

print(count)  # 6 (500*2 + 100*2 + 50*1 + 10*1)

그리디 vs DP

특징그리디DP
선택현재 최선모든 경우 고려
시간복잡도O(n log n)O(n²) ~ O(2ⁿ)
최적해 보장X (증명 필요)O
구현간단복잡

2. 대표 문제

문제 1: 회의실 배정

def max_meetings(meetings):
    """
    최대한 많은 회의 배정
    [(시작, 끝), ...]
    """
    # 끝나는 시간 기준 정렬
    meetings.sort(key=lambda x: x[1])
    
    count = 0
    last_end = 0
    
    for start, end in meetings:
        if start >= last_end:
            count += 1
            last_end = end
    
    return count

# 테스트
meetings = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9)]
print(max_meetings(meetings))  # 4
# (1,4), (5,7), (5,9) 선택 불가 → (1,4), (5,7) 선택

문제 2: 동전 거스름돈

def min_coins(coins, amount):
    """
    최소 동전 개수
    """
    coins.sort(reverse=True)
    count = 0
    
    for coin in coins:
        if amount >= coin:
            count += amount // coin
            amount %= coin
    
    return count if amount == 0 else -1

# 테스트
coins = [500, 100, 50, 10]
print(min_coins(coins, 1260))  # 6

# ❌ 그리디 실패 예
coins = [10, 7, 1]
amount = 14
# 그리디: 10 + 1 + 1 + 1 + 1 = 5개
# 최적: 7 + 7 = 2개 (DP 필요!)

문제 3: 큰 수 만들기

def make_largest_number(number, k):
    """
    숫자 k개를 제거해서 가장 큰 수 만들기
    "1924", k=2 → "94"
    """
    stack = []
    removed = 0
    
    for digit in number:
        # 스택 top이 현재보다 작으면 제거
        while stack and removed < k and stack[-1] < digit:
            stack.pop()
            removed += 1
        stack.append(digit)
    
    # 아직 k개 안 지웠으면 뒤에서 제거
    return ''.join(stack[:len(stack) - (k - removed)])

# 테스트
print(make_largest_number("1924", 2))  # "94"
print(make_largest_number("1231234", 3))  # "3234"

3. 그리디 증명

증명 방법

1) 교환 논법 (Exchange Argument)

"그리디 선택을 다른 선택으로 바꿔도 더 나아지지 않음"

2) 귀류법 (Proof by Contradiction)

"그리디가 최적해가 아니라고 가정 → 모순 도출"

예: 회의실 배정 증명

"끝나는 시간이 가장 빠른 회의를 선택하는 것이 최적"

증명:
- 다른 회의를 선택하면 끝나는 시간이 더 늦음
- 끝나는 시간이 늦으면 다음 회의 선택 폭이 줄어듦
- 따라서 끝나는 시간이 빠른 회의가 최적

4. 실전 문제

문제 1: 체육복 (프로그래머스)

def solution(n, lost, reserve):
    """
    체육복 빌려주기
    """
    # 여벌 있는데 도난당한 학생 제외
    lost_set = set(lost) - set(reserve)
    reserve_set = set(reserve) - set(lost)
    
    for r in sorted(reserve_set):
        # 앞 번호 학생부터 빌려줌
        if r - 1 in lost_set:
            lost_set.remove(r - 1)
        elif r + 1 in lost_set:
            lost_set.remove(r + 1)
    
    return n - len(lost_set)

# 테스트
print(solution(5, [2, 4], [1, 3, 5]))  # 5

문제 2: 조이스틱 (프로그래머스)

def solution(name):
    """
    조이스틱 최소 이동 횟수
    """
    answer = 0
    min_move = len(name) - 1  # 오른쪽으로만 이동
    
    for i, char in enumerate(name):
        # 상하 이동 (A → char)
        answer += min(ord(char) - ord('A'), ord('Z') - ord(char) + 1)
        
        # 좌우 이동 최적화
        next_idx = i + 1
        while next_idx < len(name) and name[next_idx] == 'A':
            next_idx += 1
        
        # 오른쪽 → 왼쪽 vs 왼쪽 → 오른쪽
        min_move = min(
            min_move,
            i + i + len(name) - next_idx,  # 오→왼
            i + len(name) - next_idx + len(name) - next_idx  # 왼→오
        )
    
    return answer + min_move

# 테스트
print(solution("JEROEN"))  # 56

실전 팁

그리디 문제 판별법

# ✅ 그리디 신호
# 1. "최대/최소" + 정렬 힌트
# 2. 시간 제한 빡빡 (O(n log n) 필요)
# 3. 반례 찾기 어려움

# ❌ 그리디 불가능
# 1. 반례 존재
# 2. "모든 경우" 고려 필요
# 3. 이전 선택이 영향 (→ DP)

정리

핵심 요약

  1. 그리디: 매 순간 최선, O(n log n)
  2. 증명 필수: 반례 없는지 확인
  3. 정렬: 대부분 정렬 후 그리디
  4. DP와 비교: 그리디가 더 빠르지만 항상 최적해는 아님

추천 문제

백준:

프로그래머스:


관련 글