← 알고리즘 강의 목록으로
💡
DP·그리디
메모이제이션·타뷸레이션·피보나치

강의 20: 동적 프로그래밍 기초 (Dynamic Programming Basics)

DP의 두 가지 접근(메모이제이션/타뷸레이션)을 피보나치·계단·동전 문제로 익힙니다.

DP메모이제이션타뷸레이션
소요 시간
약 1시간
난이도
📊 중급
선수 조건
🎯 재귀·반복문
결과물
DP의 두 가지 접근(메모이제이션/타뷸레이션)을 피보나치·계단·동전 문제로 익힙니다.

이 강의에서 배우는 것

  • 1메모이제이션(Top-down)과 타뷸레이션(Bottom-up)의 차이를 이해하고 각각 구현할 수 있다.
  • 2DP 풀이의 3단계(상태 정의 → 점화식 → 초기값)를 체계적으로 적용할 수 있다.
  • 3계단 오르기, 동전 거스름돈 등 기본 DP 문제를 스스로 상태와 점화식을 세워 풀 수 있다.

소개

동적 프로그래밍(DP)은 **복잡한 문제를 더 작은 하위 문제로 분해**하고, **이미 계산한 결과를 저장(메모이제이션)**해 중복 계산을 피하는 알고리즘 설계 기법입니다.

DP가 적용 가능한 두 조건:

  1. **겹치는 부분 문제 (Overlapping Subproblems)**: 같은 하위 문제가 반복 등장
  2. **최적 부분 구조 (Optimal Substructure)**: 전체 최적해가 부분 문제의 최적해로 구성
text
단순 재귀 vs DP — 피보나치 예시

단순 재귀 fib(5):
    fib(5)
   /      \
fib(4)   fib(3)    ← fib(3) 중복!
 /   \   /   \
fib(3) fib(2) fib(2) fib(1)   ← fib(2) 3번 중복!
...
→ 시간: O(2^n), 공간: O(n)

DP 메모이제이션 fib(5):
    fib(5)
   /      \
fib(4)   [캐시: 3]  ← 이미 계산됨
 /   \
fib(3) [캐시: 2]
...
→ 시간: O(n), 공간: O(n)

핵심 개념

DP 설계 3단계

text
1. 상태(State) 정의:
   dp[i] = "i번째까지 고려했을 때 ..."
   dp[i][j] = "i번째, j번째까지 고려했을 때 ..."

2. 점화식(Recurrence Relation):
   dp[i] = f(dp[i-1], dp[i-2], ...)

3. 초기값(Base Case):
   dp[0] = ..., dp[1] = ...

메모이제이션 vs 타뷸레이션

text
┌──────────────────┬────────────────────────┬──────────────────────────────┐
│ 특성             │ 메모이제이션 (Top-down) │ 타뷸레이션 (Bottom-up)       │
├──────────────────┼────────────────────────┼──────────────────────────────┤
│ 방향             │ 큰 문제 → 작은 문제     │ 작은 문제 → 큰 문제          │
│ 구현             │ 재귀 + 캐시             │ 반복문 + 테이블              │
│ 필요한 계산      │ 실제 필요한 것만        │ 모든 하위 문제 계산          │
│ 공간             │ O(n) + 재귀 스택        │ O(n) (스택 없음)             │
│ 속도             │ 함수 호출 오버헤드      │ 보통 더 빠름                 │
│ 구현 난이도      │ 직관적                  │ 순서 고려 필요               │
└──────────────────┴────────────────────────┴──────────────────────────────┘

피보나치 복잡도 비교

text
┌──────────────────────┬───────────┬──────────┐
│ 방법                 │ 시간      │ 공간     │
├──────────────────────┼───────────┼──────────┤
│ 단순 재귀            │ O(2^n)    │ O(n)     │
│ 메모이제이션         │ O(n)      │ O(n)     │
│ 타뷸레이션           │ O(n)      │ O(n)     │
│ 공간 최적화          │ O(n)      │ O(1)     │
└──────────────────────┴───────────┴──────────┘

계단 오르기 점화식

text
dp[i] = i번째 계단까지 오르는 경우의 수 (한 번에 1칸 또는 2칸)

dp[1] = 1   (1칸짜리: {1})
dp[2] = 2   (2칸짜리: {1,1}, {2})
dp[i] = dp[i-1] + dp[i-2]   (마지막에 1칸 왔거나 2칸 왔거나)

→ 피보나치와 동일한 점화식!

동전 거스름돈 (DP가 필요한 이유)

text
동전: [1, 5, 6, 9], 거스름돈: 11

그리디: 9 + 1 + 1 = 3개
DP:     5 + 6 = 2개  ← 최적!

그리디가 실패하는 이유: 지역 최적해 ≠ 전역 최적해

DP 점화식:
dp[i] = i원을 만드는 데 필요한 최소 동전 수
dp[0] = 0
dp[i] = min(dp[i - coin] + 1) for coin in coins if coin ≤ i

예제로 보기

파일내용
`src/01_fibonacci_memo.py`피보나치: 단순/메모이제이션/@lru_cache
`src/02_fibonacci_tab.py`피보나치: 타뷸레이션, 공간 O(1)
`src/03_climbing_stairs.py`계단 오르기, 변형 문제들
`src/04_coin_change.py`동전 최소 개수, 경우의 수

자주 하는 실수

  1. **상태 정의 불명확**: `dp[i]`가 무엇을 의미하는지 먼저 명확히 정의하지 않으면 점화식을 세울 수 없음
  2. **초기값(Base Case) 누락**: dp[0], dp[1] 초기화를 빠뜨리거나 잘못 설정하면 모든 계산이 틀림
  3. **점화식 방향 오류**: Bottom-up에서 dp[i]를 계산할 때 필요한 dp[j] (j < i)가 이미 계산됐는지 확인
  4. **배열 인덱스 범위 초과**: `dp[i - coin]`에서 `i - coin < 0`인 경우 처리 누락
  5. **그리디와 혼동**: "항상 가장 큰 것을 선택"이 최적이 아닌 경우 DP 필요 — 그리디 실패 예시를 항상 확인

정리

  • DP = **메모이제이션** + **최적 부분 구조** + **겹치는 부분 문제**
  • Top-down(재귀+캐시) vs Bottom-up(반복+테이블)
  • 설계 3단계: 상태 정의 → 점화식 → 초기값
  • 공간 최적화: 이전 2개만 필요하면 O(1)로 줄일 수 있음

직접 해 보기

  1. n번째 피보나치를 행렬 거듭제곱으로 O(log n)에 구하기
  2. 최장 증가 부분 수열(LIS) — 다음 강의 예습
  3. 격자(grid) DP: m×n 격자 왼쪽 위에서 오른쪽 아래까지 경로 수

과제

📂 `homework/README.md` 참조

  • 백준 2579 (계단 오르기) — Silver III
  • 백준 1463 (1로 만들기) — Silver III
  • 백준 11726 (2×n 타일링) — Silver III
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗