math
CHAPTER 31 / 45
읽기 약 2분
FUNCTION
동적 프로그래밍의 수학
핵심 개념
최적 부분 구조 + 중복 부분 문제 — Top-down(메모) vs Bottom-up(테이블) + LCS·배낭.
본문
동적 프로그래밍 = 두 조건
1. 최적 부분 구조 (Optimal Substructure)
부분 문제의 최적해 → 전체 문제의 최적해
예: 최단 경로 — 부분 경로도 최단
2. 중복 부분 문제 (Overlapping Subproblems)
같은 부분 문제가 여러 번 등장
예: 피보나치 — fib(5) 계산 중 fib(3) 여러 번Top-down (메모이제이션)
# 재귀 + 캐시 — 풀어 나가면서 계산 결과 저장
def lcs_topdown(s1, s2):
"""최장 공통 부분수열 — 메모이제이션"""
cache = {}
def helper(i, j):
if i == 0 or j == 0:
return 0
if (i, j) in cache:
return cache[(i, j)]
if s1[i-1] == s2[j-1]:
cache[(i, j)] = helper(i-1, j-1) + 1
else:
cache[(i, j)] = max(helper(i-1, j), helper(i, j-1))
return cache[(i, j)]
return helper(len(s1), len(s2))
print(lcs_topdown("ABCBDAB", "BDCAB")) # 4 (BCAB 또는 BDAB)Bottom-up (테이블)
# 작은 문제부터 표 채우며 큰 문제로
def lcs_bottomup(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
print(lcs_bottomup("ABCBDAB", "BDCAB")) # 40-1 배낭 문제
# N개 물건, 각 무게 wi와 가치 vi
# 배낭 용량 W에서 최대 가치 가져가기
# 점화식: dp[i][w] = max(dp[i-1][w], dp[i-1][w-wi] + vi)
def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i-1] <= w:
dp[i][w] = max(
dp[i-1][w], # 안 가져감
dp[i-1][w - weights[i-1]] + values[i-1] # 가져감
)
else:
dp[i][w] = dp[i-1][w]
return dp[n][capacity]
# 예: 4개 물건, 용량 5
weights = [1, 2, 3, 2]
values = [4, 5, 6, 3]
capacity = 5
print(knapsack(weights, values, capacity)) # 13
# 시간복잡도: O(n × W)
# 공간복잡도: O(n × W) — 1D dp로 줄이면 O(W)동전 거스름돈
# 동전 [1, 2, 5]로 11원 만드는 최소 동전 수
# 점화식: dp[amount] = min(dp[amount - coin] + 1) for coin in coins
def min_coins(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for coin in coins:
if a - coin >= 0:
dp[a] = min(dp[a], dp[a - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
print(min_coins([1, 2, 5], 11)) # 3 (5+5+1)
print(min_coins([2], 3)) # -1 (불가능)최장 증가 부분수열 (LIS)
# 점화식: dp[i] = max(dp[j] + 1) for j < i if arr[j] < arr[i]
def lis(arr):
if not arr:
return 0
n = len(arr)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if arr[j] < arr[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
print(lis([10, 9, 2, 5, 3, 7, 101, 18])) # 4 (2, 3, 7, 18 또는 2, 3, 7, 101)
# 더 빠른 — 이진 탐색 활용 O(n log n)
import bisect
def lis_fast(arr):
tails = []
for x in arr:
i = bisect.bisect_left(tails, x)
if i == len(tails):
tails.append(x)
else:
tails[i] = x
return len(tails)
print(lis_fast([10, 9, 2, 5, 3, 7, 101, 18])) # 4편집 거리 (Edit Distance)
# 두 문자열을 같게 만드는 최소 편집 (삽입/삭제/교체)
# 점화식: 3가지 경우의 최솟값
def edit_distance(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 기저: 빈 문자열로 변환 = 길이만큼 삭제/삽입
for i in range(m + 1): dp[i][0] = i
for j in range(n + 1): dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] # 같으면 그대로
else:
dp[i][j] = min(
dp[i-1][j] + 1, # 삭제
dp[i][j-1] + 1, # 삽입
dp[i-1][j-1] + 1, # 교체
)
return dp[m][n]
print(edit_distance("kitten", "sitting")) # 3
# kitten → sitten (k→s, 교체)
# sitten → sittin (e→i, 교체)
# sittin → sitting (n→ng, 삽입)다음 챕터
CH.32 "그리디 알고리즘" — DP의 사촌 + 언제 그리디가 최적인가.
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 알고리즘 문제를 DP로 변환할 수 있는지 분석하고 점화식과 풀이 단계를 만들어줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
한국 코딩 테스트 DP 문제 Top 10과 각각의 점화식 + 풀이 패턴을 비교해줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 코드의 재귀 함수 중 DP로 최적화 가능한 위치를 자동 식별해서 예상 성능 향상을 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 IT 대기업 면접에서 DP 출제 비율과 평가 깊이를 솔직히 알려줘.
⭐ 이것만 기억하세요
동적 프로그래밍의 수학은 이 3가지만 확실히 잡으세요
1.DP 두 조건: 최적 부분 구조 + 중복 부분 문제 — 둘 다 만족할 때 적용 가능
2.Top-down(메모) vs Bottom-up(테이블) — 결과 동일, 구현 스타일 차이 (메모는 직관적, 테이블은 빠름)
3.다음 챕터 CH.32에서 그리디 — DP보다 단순하지만 항상 최적은 아님
공유하기
진행도 31 / 45