본문으로 바로가기
제품데스크톱 도구TASTECAD도면 확인TASTEFILES파일 관리TASTEFTP파일 전송TASTESSH원격 작업공간TASTEZIP압축과 해제개발 도구TASTECODERust 통합 개발 환경TASTESTUDIO통합 개발·테스트 환경TASTEETL데이터 통합(ETL)서버TASTEREMOTE원격 데스크톱 서버TASTEGEO공간정보 서버TASTEMAIL메일 작업공간시작하기다운로드문서구매모든 제품
마켓플레이스
소식
개발소식오늘의 개발 소식개발 문서프로그래밍 언어RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron알고리즘알고리즘 기초알고리즘 핵심알고리즘 심화실무 알고리즘
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스커뮤니티 안내
협업기술 전수홈페이지 구축데스크톱 앱 구축릴리스·결제 파이프라인판매 파트너문의
서비스원격 데스크톱설치 패키지 커스텀
TASTEDEV
제품

데스크톱 도구

TASTECAD복잡한 도면을 가볍게 열다TASTEFILES필요한 파일만, 가장 빠르게TASTEFTP파일이 오가는 가장 단정한 길TASTESSH서버와 나 사이, 더 단단한 연결TASTEZIP더 빠르게 묶고, 더 안전하게 풀다

개발 도구

TASTECODERust 앱을 편집 · 빌드 · 디버깅 · 테스트까지 한 창에서TASTESTUDIO편집부터 원격 테스트와 승인 기반 AI 수정까지TASTEETL값을 보며 데이터를 옮깁니다

서버

TASTEREMOTE우리 회사 서버로 운영하는 원격 데스크톱TASTEGEOGeoServer 호환 공간정보 서버를 Rust로 다시 세웁니다TASTEMAIL메일의 주도권을 다시 내 손에

시작하기

다운로드모든 제품 최신 판문서제품 사용법구매구독 · 영구 라이선스모든 제품모든 제품 한눈에
모든 제품
마켓플레이스소식
개발

소식

오늘의 개발 소식언어 · 프레임워크 · GitHub · AI 공식 발표개발 문서언어 · 프레임워크 · 알고리즘 안내서

프로그래밍 언어

RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart

프레임워크

프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular
서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express
앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron

알고리즘

알고리즘 기초복잡도 분석 · 정렬 · 탐색 · 기본 자료구조 · 트리와 힙알고리즘 핵심그래프 탐색 · 최단 경로와 최소 신장 트리 · 동적 계획법 · 탐욕 알고리즘 · 분할 정복 · 백트래킹알고리즘 심화문자열 알고리즘 · 수학과 정수론 · 비트 연산 · 구간 트리 · 유니온 파인드실무 알고리즘코딩 면접 풀이 전략 · LRU 캐시 · 레이트 리미터 · 일관된 해시
개발 문서 전체 보기
커뮤니티
프로젝트빌더와 작업 발견개발 로그결정과 시행착오프롬프트재사용 가능한 지시AI 도구 이슈Claude · Codex · Gemini 문제와 우회법쇼케이스완성된 결과커뮤니티 안내공개하고 함께 만드는 방식
협업
기술 전수Rust·egui 네이티브 앱 만들기홈페이지 구축10개 언어, 계정, 문서·다운로드데스크톱 앱 구축Rust·egui, WebView 없이릴리스·결제 파이프라인서명 · MSI·deb·rpm·AppImage · 해시판매 파트너소개하고 결제액의 30% 받기문의구축 · 제휴 · 투자 문의
서비스
원격 데스크톱브라우저로 내 컴퓨터에 접속설치 패키지 커스텀필요한 기능만, 설정은 미리
구매
KO
한국어EnglishDeutschEspañolFrançaisItalianoPortuguês (Brasil)日本語简体中文繁體中文
로그인
로그인
TASTEDEV

작은 도구부터, 더 좋은 개발까지.

support@tastedev.netTASTEDEV 후원하기
제품제품군구매문서다운로드마켓플레이스고객 지원
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스
정책개인정보처리방침이용약관구독 약관환불 정책커뮤니티 운영 정책
계정로그인가입하기
방문자
오늘
012345678901234567890123456789012345678901234567890123456789
어제
012345678901234567890123456789012345678901234567890123456789
누적 조회
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
상호
지엑스소프트
대표자
최영구
사업자등록번호
853-44-00368
주소
인천광역시 미추홀구 경인로 351 9층 V24
전화
+82-10-4404-1523
이메일
whitecalvin@tastedev.net
개인정보 보호책임자
최영구

© 2026 TASTEDEV제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

동적 계획법 문서
동적 계획법
개념동작 원리구현복잡도연습 문제실무 활용
토론

동적 계획법 안내서 · 1/6

개념

동적 계획법(Dynamic Programming, DP)은 문제를 더 작은 부분 문제로 나누고, 각 부분 문제를 한 번만 풀어 답을 저장해 두었다가 같은 부분 문제가 다시 나오면 저장한 답을 꺼내 쓰는 방법입니다. 1950년대에 리처드 벨먼(Richard Bellman)이 정리한 방법으로, 여기서 "programming"은 코드 작성이 아니라 표를 채워 가며 계획을 세운다는 뜻입니다. 이 장에서는 DP가 언제 통하는지, 그 바탕이 되는 두 가지 성질, 그리고 이후 장에서 쓰는 용어를 정리합니다.

DP가 성립하는 두 가지 성질

문제가 다음 두 성질을 모두 가지면 DP로 풀기 좋습니다.

  • 겹치는 부분 문제(overlapping subproblems): 단순한 재귀 풀이가 같은 부분 문제를 여러 번 풉니다. 결과를 저장해 두면 반복 계산이 표 조회 한 번으로 바뀝니다.
  • 최적 부분 구조(optimal substructure): 전체 문제의 최적 해를 부분 문제의 최적 해로 조립할 수 있습니다. 예를 들어 B를 거쳐 A에서 C로 가는 최단 경로에는 A에서 B까지의 최단 경로가 들어 있습니다.

분할 정복도 문제를 부분 문제로 나누지만, 병합 정렬의 두 절반은 서로 겹치지 않으므로 다시 쓸 답이 없습니다. 그리디 알고리즘도 최적 부분 구조에 기대지만 매 단계에서 당장 가장 좋아 보이는 선택 하나만 확정합니다. DP는 가능한 선택을 모두 따져 보고 가장 좋은 것을 고르기 때문에 그리디가 틀리는 문제에서도 정확한 답을 냅니다.

겹침을 눈으로 확인하기: 피보나치

피보나치 수는 F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)로 정의됩니다. 정의를 그대로 코드로 옮기면 답은 맞지만, 같은 값을 계속 다시 계산하므로 매우 느립니다.

calls = 0


def fib(n: int) -> int:
    global calls
    calls += 1
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)


print(fib(25), calls)  # 75025 242785

F(25) 하나를 구하는 데 함수가 약 24만 번 호출되고, 호출 수는 대략 1.6^n의 속도로 늘어납니다. 그런데 서로 다른 부분 문제는 F(0)부터 F(25)까지 26개뿐입니다. "실제 호출 수"와 "서로 다른 부분 문제 수" 사이의 이 차이를 없애는 것이 DP입니다.

답을 기억하는 두 가지 방법

메모이제이션(memoization, 하향식)은 재귀 구조를 그대로 두고 캐시를 붙입니다. 어떤 인자로 처음 호출되면 계산하고, 그 뒤에는 저장된 값을 돌려줍니다. Python에서는 functools.cache 데코레이터 하나로 됩니다.

from functools import cache


@cache
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)


print(fib(90))           # 2880067194370816120
print(fib.cache_info())  # CacheInfo(hits=88, misses=91, maxsize=None, currsize=91)

타뷸레이션(tabulation, 상향식)은 재귀를 아예 쓰지 않습니다. 각 부분 문제가 의존하는 값이 항상 먼저 계산되는 순서를 정하고, 그 순서대로 반복문으로 표를 채웁니다.

def fib(n: int) -> int:
    table = [0] * (n + 1)
    if n > 0:
        table[1] = 1
    for i in range(2, n + 1):
        table[i] = table[i - 1] + table[i - 2]
    return table[n]


print(fib(90))  # 2880067194370816120

두 방식 모두 n + 1개의 값을 한 번씩만 계산하므로 시간은 O(n)입니다. 메모이제이션은 보통 작성이 더 빠르고, 시작 상태에서 실제로 도달하는 상태만 계산합니다. 타뷸레이션은 재귀 깊이 제한과 함수 호출 비용이 없고, 메모리를 줄이는 최적화가 눈에 잘 보입니다.

용어 정리

용어뜻피보나치에서
상태(state)부분 문제 하나를 가리키는 매개변수n
점화식(전이, transition)작은 상태의 답으로 큰 상태의 답을 만드는 규칙F(n) = F(n-1) + F(n-2)
기저 사례(base case)재귀 없이 바로 답하는 상태F(0) = 0, F(1) = 1
표 / 메모답을 저장하는 곳리스트 table 또는 @cache의 딕셔너리
계산 순서상향식에서 표를 채우는 순서n이 커지는 순서
최종 답돌려줄 상태(또는 상태들의 조합)F(n)

언제 DP를 떠올릴까

문제가 다음 중 하나를 묻고, 상태 표가 메모리에 들어갈 만큼 입력이 작다면 DP를 먼저 검토합니다.

  • 최솟값이나 최댓값: 가장 적은 동전 수, 가장 싼 경로, 가방에 담을 수 있는 최대 가치
  • 경우의 수: 격자 경로의 수, 계단을 오르는 방법의 수, 문자열을 해석하는 방법의 수
  • 가능 여부: 집합을 합이 같은 두 묶음으로 나눌 수 있는지, 문자열을 사전 단어로 쪼갤 수 있는지

풀이가 선택을 차례로 내리는 모양이고, 선택 하나를 내린 뒤 남은 문제가 원래 문제의 작은 판처럼 보인다면 강한 신호입니다. 서로 다른 선택 순서가 같은 남은 문제로 이어진다면 부분 문제가 겹친다는 뜻입니다. 코딩 테스트에서 "방법의 수를 1,000,000,007로 나눈 나머지" 같은 문구가 보이면 거의 항상 DP 문제입니다.

정리

  • DP는 서로 다른 부분 문제를 한 번씩만 풀고 저장한 답을 다시 씁니다.
  • 겹치는 부분 문제와 최적 부분 구조가 있어야 합니다.
  • 메모이제이션은 재귀에 캐시를 붙이고, 타뷸레이션은 의존 순서대로 표를 채웁니다.
  • 모든 DP 풀이는 상태, 점화식, 기저 사례, 계산 순서, 최종 답으로 설명할 수 있습니다.
이 장에 대해 질문이 있나요?
다음 장동작 원리

댓글 0개

인기 댓글순최신순

로그인 · 로그인하면 댓글을 남길 수 있습니다.