노트

동적 프로그래밍

Dynamic Programming

CS#algorithm · 연결된 개념 6개

쉽게 말하면

동적 프로그래밍은 숙제를 풀다 구한 중간 답을 공책 귀퉁이에 적어 두고, 같은 계산이 또 나오면 다시 풀지 않고 베껴 쓰는 방법이에요. 같은 계산을 수없이 반복하던 피보나치가 금방 끝나요.

비유가 깨지는 곳 적어 두기만 하면 다 되는 건 아니에요. 같은 부분 문제가 겹치고, 부분 답을 조합해 전체 최적해를 만들 수 있어야 해요. 재귀에 캐시를 단 메모이제이션은 n이 크면 스택이 넘칠 수 있어요.

복잡한 문제를 더 작은 부분 문제로 나눠 풀되, 한 번 푼 부분 문제의 답을 저장해 다시 계산하지 않는 방법. 두 조건이 맞을 때 쓴다.

  • 겹치는 부분 문제(Overlapping Subproblems): 같은 부분 문제가 여러 번 나온다
  • 최적 부분 구조(Optimal Substructure): 부분 문제의 최적해를 조합해 전체 최적해를 만들 수 있다

피보나치로 보기

단순 재귀 fib(n) = fib(n-1) + fib(n-2)는 같은 값을 수없이 다시 계산해 O(2ⁿ)이다. fib(45)만 돼도 눈에 띄게 느리다.

// 메모이제이션: 위에서 아래로(top-down), 재귀 + 캐시
const memo = new Map<number, number>()
function fibMemo(n: number): number {
  if (n <= 2) return 1
  if (!memo.has(n)) memo.set(n, fibMemo(n - 1) + fibMemo(n - 2))
  return memo.get(n)!
}
 
// 타뷸레이션: 아래에서 위로(bottom-up), 반복문 + 표
function fibTable(n: number) {
  const t = [0, 1, 1]
  for (let i = 3; i <= n; i++) t[i] = t[i - 1] + t[i - 2]
  return t[n]
}

둘 다 O(n)이 된다.

  • 메모이제이션(Memoization)은 원래 재귀 구조를 그대로 두고 캐시만 더해 쓰기 쉽다. 하지만 n이 크면 재귀가 깊어져 스택 오버플로가 날 수 있다(재귀)
  • 타뷸레이션(Tabulation)은 작은 문제부터 표를 채운다. 스택을 쓰지 않고, 필요한 칸만 남기면 공간도 줄일 수 있다(피보나치는 변수 두 개면 된다)

배낭 문제(Knapsack Problem), 최장 공통 부분 수열(Longest Common Subsequence, LCS), 편집 거리(Edit Distance), 동전 거스름돈(Coin Change)이 대표 문제다. 부분 문제가 겹치지 않는다면 분할 정복으로 충분하다. 같은 "계산 결과 재사용" 아이디어가 React의 memo·useMemo·useCallback과 캐시 전반(읽기 캐시 전략)에도 있다.

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 섣부른 최적화

    "섣부른 최적화는 모든 악의 근원이다." 도널드 크누스(Donald Knuth)가 1974년 글 「Structured Programming with go to Statements」에서 쓴 말이다. 원문의 맥락은 작은 효율은 대부분(약 97%)의 경우 잊으라는 것이고, 정말 중요한 3%는 놓치지 말라는 말이 이어진다.

  • 청킹과 코드 읽기

    여러 정보를 의미 있는 덩어리 하나로 묶어 기억하는 것. 아는 것이 많을수록 코드를 큰 덩어리로 읽는다.

  • 구성요소 줄이기

    셀 수 있는 모든 구성요소(파일, 폴더 깊이, 클래스, 함수, 분기, 변수, 테이블, 필드, 테스트 케이스…)는 비용이라는 관점. 심플 디자인의 네 번째 규칙을 실무 기준으로 풀어낸 것이다.

  • 최종 일관성

    최종 일관성(eventual consistency)은 "지금 당장은 저장소마다 값이 다를 수 있지만, 새 변경이 멈추면 결국 같아진다"는 보장이다. 원본 DB와 검색 색인·캐시·다른 서비스처럼 물리적으로 분리된 저장소를 한 트랜잭션으로 묶을 수 없을 때 받아들이는 일관성 모델(consistency model)이다.

  • 인지 부하

    작업 기억이 한 번에 처리해야 하는 양. 문제 자체의 복잡함과 표현 방식에서 오는 복잡함으로 나뉜다.

보기 옵션