💡
DP·그리디
메모이제이션·타뷸레이션·피보나치
강의 20: 동적 프로그래밍 기초 (Dynamic Programming Basics)
DP의 두 가지 접근(메모이제이션/타뷸레이션)을 피보나치·계단·동전 문제로 익힙니다.
DP메모이제이션타뷸레이션
소요 시간
⏱ 약 1시간
난이도
📊 중급
선수 조건
🎯 재귀·반복문
결과물
DP의 두 가지 접근(메모이제이션/타뷸레이션)을 피보나치·계단·동전 문제로 익힙니다.
이 강의에서 배우는 것
- 1메모이제이션(Top-down)과 타뷸레이션(Bottom-up)의 차이를 이해하고 각각 구현할 수 있다.
- 2DP 풀이의 3단계(상태 정의 → 점화식 → 초기값)를 체계적으로 적용할 수 있다.
- 3계단 오르기, 동전 거스름돈 등 기본 DP 문제를 스스로 상태와 점화식을 세워 풀 수 있다.
소개
동적 프로그래밍(DP)은 **복잡한 문제를 더 작은 하위 문제로 분해**하고, **이미 계산한 결과를 저장(메모이제이션)**해 중복 계산을 피하는 알고리즘 설계 기법입니다.
DP가 적용 가능한 두 조건:
- **겹치는 부분 문제 (Overlapping Subproblems)**: 같은 하위 문제가 반복 등장
- **최적 부분 구조 (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` | 동전 최소 개수, 경우의 수 |
자주 하는 실수
- **상태 정의 불명확**: `dp[i]`가 무엇을 의미하는지 먼저 명확히 정의하지 않으면 점화식을 세울 수 없음
- **초기값(Base Case) 누락**: dp[0], dp[1] 초기화를 빠뜨리거나 잘못 설정하면 모든 계산이 틀림
- **점화식 방향 오류**: Bottom-up에서 dp[i]를 계산할 때 필요한 dp[j] (j < i)가 이미 계산됐는지 확인
- **배열 인덱스 범위 초과**: `dp[i - coin]`에서 `i - coin < 0`인 경우 처리 누락
- **그리디와 혼동**: "항상 가장 큰 것을 선택"이 최적이 아닌 경우 DP 필요 — 그리디 실패 예시를 항상 확인
정리
- DP = **메모이제이션** + **최적 부분 구조** + **겹치는 부분 문제**
- Top-down(재귀+캐시) vs Bottom-up(반복+테이블)
- 설계 3단계: 상태 정의 → 점화식 → 초기값
- 공간 최적화: 이전 2개만 필요하면 O(1)로 줄일 수 있음
직접 해 보기
- n번째 피보나치를 행렬 거듭제곱으로 O(log n)에 구하기
- 최장 증가 부분 수열(LIS) — 다음 강의 예습
- 격자(grid) DP: m×n 격자 왼쪽 위에서 오른쪽 아래까지 경로 수
과제
📂 `homework/README.md` 참조
- 백준 2579 (계단 오르기) — Silver III
- 백준 1463 (1로 만들기) — Silver III
- 백준 11726 (2×n 타일링) — Silver III