Algorithm#알고리즘#DP#다이나믹프로그래밍#점화식#메모이제이션#코딩테스트

다이나믹 프로그래밍 (1)

DP 개념 & 점화식 도출 · Top-Down vs Bottom-Up · 1차원 DP 대표 유형 · 피보나치, 계단 오르기, 동전 교환 등

2026년 4월 8일4분 읽기조회 51
공유

학습 목표: DP 개념 & 점화식 도출 · Top-Down vs Bottom-Up · 1차원 DP 대표 유형 · 피보나치, 계단 오르기, 동전 교환 등


1️⃣ DP란 무엇인가?

개념

큰 문제를 작은 부분 문제로 나누고, 이미 계산한 결과를 저장해서 재사용하는 기법.

🧾 비유: 수학 시험에서 한 번 푼 계산 중간값을 지우지 않고 풀이 옆에 적어두는 것!
같은 계산을 또 할 때 다시 풀지 않고 옆에 적어둔 값을 그대로 쓴다.


DP가 적용 가능한 조건 2가지

  1. 최적 부분 구조 (Optimal Substructure)

    → 전체 문제의 최적해가 부분 문제의 최적해로 구성됨

    → 예: 최단 경로의 부분 경로도 최단 경로

  2. 중복 부분 문제 (Overlapping Subproblems)

    → 같은 부분 문제가 여러 번 반복해서 등장함

    → 예: fib(5) 계산할 때 fib(3)이 여러 번 등장

이 두 조건이 모두 성립하면 → DP 적용 가능!
그리디는 매 순간 최선, DP는 모든 경우를 저장하며 최선 을 찾음.


DP vs 재귀 vs 그리디

  • 재귀: 같은 계산을 중복해서 함 → 느림
  • 재귀 + 메모이제이션 : 결과를 저장 → Top-Down DP
  • 반복문 + 테이블 : 작은 것부터 채워나감 → Bottom-Up DP
  • 그리디 : 저장 없이 매 순간 최선 선택 → 빠르지만 항상 최적 아님

2️⃣ Top-Down vs Bottom-Up

Top-Down (메모이제이션, Memoization)

재귀로 구현하되 이미 계산한 결과는 딕셔너리/배열에 저장. 필요할 때만 계산하는 방식 (Lazy Evaluation).

# 피보나치 - Top-Down
def fib_topdown(n, memo={}):
    if n <= 1:
        return n
    if n in memo:           # 이미 계산했으면 바로 반환
        return memo[n]
    memo[n] = fib_topdown(n-1, memo) + fib_topdown(n-2, memo)
    return memo[n]

print(fib_topdown(10))  # 55

✅ 장점: 필요한 부분만 계산, 재귀적 사고가 직관적
❌ 단점: 재귀 깊이 한계, 함수 호출 오버헤드


Bottom-Up (타뷸레이션, Tabulation)

작은 문제부터 순서대로 테이블을 채워나가는 방식. 반복문으로 구현, 재귀 없음.

# 피보나치 - Bottom-Up
def fib_bottomup(n):
    if n <= 1:
        return n

    dp = [0] * (n + 1)
    dp[0] = 0
    dp[1] = 1

    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]  # 점화식!

    return dp[n]

print(fib_bottomup(10))  # 55

✅ 장점: 재귀 없어서 스택 오버플로우 걱정 없음, 속도 빠름
❌ 단점: 필요 없는 부분도 계산할 수 있음


언제 뭘 쓰냐

코테에서 일반적으로          → Bottom-Up 추천
점화식 관계가 복잡할 때      → Top-Down이 직관적
n이 크거나 재귀 깊이 우려 시 → Bottom-Up

3️⃣ 점화식 도출 방법

DP의 핵심은 점화식(Recurrence Relation) 세우기야.

점화식 도출 3단계

1. dp[i]의 의미를 명확하게 정의한다
   → "dp[i] = i번째 계단까지 올라가는 최대 점수"

2. 마지막 선택(결정)이 무엇인지 생각한다
   → "i번째 계단에 도달하려면 i-1 또는 i-2에서 왔을 것"

3. 그 선택들 중 최적을 고른다
   → dp[i] = max(dp[i-1], dp[i-2]) + score[i]

핵심 사고법:"dp[i]를 구하려면 dp[i-?]에서 어떻게 왔냐?" 를 생각하기


4️⃣ 1차원 DP 대표 유형

유형 1 - 피보나치 수열

# dp[i] = i번째 피보나치 수
# dp[i] = dp[i-1] + dp[i-2]

def fibonacci(n):
    dp = [0] * (n + 1)
    dp[1] = 1

    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]

    return dp[n]

print(fibonacci(10))  # 55

유형 2 - 1로 만들기

정수 N에 3가지 연산을 최소한으로 사용해서 1을 만들어라

  1. 3으로 나누어 떨어지면 3으로 나누기
  2. 2로 나누어 떨어지면 2로 나누기
  3. 1 빼기
# dp[i] = i를 1로 만드는 최소 연산 횟수
# dp[i] = min(dp[i//3], dp[i//2], dp[i-1]) + 1

def make_one(n):
    dp = [0] * (n + 1)

    for i in range(2, n + 1):
        dp[i] = dp[i-1] + 1      # 무조건 -1 가능

        if i % 3 == 0:
            dp[i] = min(dp[i], dp[i//3] + 1)

        if i % 2 == 0:
            dp[i] = min(dp[i], dp[i//2] + 1)

    return dp[n]

print(make_one(10))  # 3  (10 → 9 → 3 → 1)

점화식 도출 과정:

dp[1] = 0 (이미 1이니까)
dp[2] = 1 (2 ÷ 2 = 1, 1번)
dp[3] = 1 (3 ÷ 3 = 1, 1번)
dp[4] = 2 (4 ÷ 2 = 2 ÷ 2 = 1, 2번)
dp[10] = 3 (10 → 9 → 3 → 1)

유형 3 - 계단 오르기

계단마다 점수가 있고, 규칙이 있을 때 최대 점수를 구해라

  1. 한 번에 1칸 또는 2칸만 오를 수 있다
  2. 연속된 3개의 계단을 밟으면 안 된다
  3. 마지막 계단은 반드시 밟아야 한다
# dp[i] = i번째 계단까지 올라갈 때 최대 점수
# 경우 1: i-1 계단에서 1칸 올라옴 → 연속 2칸이면 i-3에서 왔어야 함
#          dp[i] = dp[i-3] + score[i-1] + score[i]
# 경우 2: i-2 계단에서 2칸 올라옴 (i-1 안 밟음)
#          dp[i] = dp[i-2] + score[i]

def stair(scores):
    n = len(scores)
    if n == 1:
        return scores[0]
    if n == 2:
        return scores[0] + scores[1]

    dp = [0] * n
    dp[0] = scores[0]
    dp[1] = scores[0] + scores[1]
    dp[2] = max(scores[0] + scores[2], scores[1] + scores[2])

    for i in range(3, n):
        # 경우 1: i-1도 밟고 i도 밟음 (i-2는 못 밟음)
        case1 = dp[i-3] + scores[i-1] + scores[i]
        # 경우 2: i-1은 밟지 않고 i만 밟음
        case2 = dp[i-2] + scores[i]
        dp[i] = max(case1, case2)

    return dp[n-1]

scores = [10, 20, 15, 25, 10, 20]
print(stair(scores))  # 75  (10+25+20+20)

핵심: 마지막 계단(i)을 밟는 방법은 딱 2가지!

방법 1: i-2 → i (i-1은 건너뜀)
방법 2: i-3 → i-1 → i (연속 2칸만)
→ i-2 → i-1 → i (연속 3칸) 은 금지!

유형 4 - 동전 교환 (최소 동전 수)

n원을 만드는 데 필요한 동전의 최소 개수는?

# dp[i] = i원을 만드는 최소 동전 수
# dp[i] = min(dp[i - coin] + 1) for coin in coins if i >= coin

def min_coins(n, coins):
    dp = [float('inf')] * (n + 1)
    dp[0] = 0      # 0원은 동전 0개

    for i in range(1, n + 1):
        for coin in coins:
            if i >= coin and dp[i - coin] != float('inf'):
                dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[n] if dp[n] != float('inf') else -1

coins = [1, 5, 10, 25]
print(min_coins(36, coins))   # 3  (25+10+1)
print(min_coins(11, coins))   # 2  (10+1)

점화식 도출 과정:

dp[0] = 0
dp[1] = dp[0] + 1 = 1 (1짜리 동전)
dp[5] = dp[0] + 1 = 1 (5짜리 동전)
dp[6] = dp[5] + 1 = 2 (5짜리 + 1짜리)
dp[11] = dp[10] + 1 = 2 (10짜리 + 1짜리)

유형 5 - 동전 교환 (경우의 수)

n원을 만드는 방법의 수는? (동전 종류 순서 무관)

# dp[i] = i원을 만드는 방법의 수

def count_coins(n, coins):
    dp = [0] * (n + 1)
    dp[0] = 1   # 0원 만드는 방법: 아무것도 안 쓰는 1가지

    for coin in coins:
        for i in range(coin, n + 1):
            dp[i] += dp[i - coin]

    return dp[n]

coins = [1, 2, 5]
print(count_coins(5, coins))  # 4
# (1+1+1+1+1), (1+1+1+2), (1+2+2), (5)

⚠️ 최소 동전 수 vs 경우의 수 차이:

최소 동전 수: coin 루프 안에 i 루프 → dp[i] = min(...)
경우의 수:    coin 루프 안에 i 루프 → dp[i] += dp[i-coin]
루프 순서는 같지만 점화식이 다른거!!!

5️⃣ 점화식 도출 과정 발표 예시

DP 문제 풀이 과정을 말로 설명할 때의 구조임

예시 - 계단 오르기 발표

  • dp 정의:
    • dp[i] = i번째 계단까지 도달했을 때 얻을 수 있는 최대 점수
  • 기저 조건: dp[0] = score[0] dp[1] = score[0] + score[1] dp[2] = max(score[0]+score[2], score[1]+score[2])
  • 점화식: i번째 계단을 밟는 경우는 2가지
    • i-1도 밟은 경우: i-2는 못 밟으므로 dp[i-3] + score[i-1] + score[i]
    • i-1을 안 밟은 경우: dp[i-2] + score[i] → dp[i] = max(dp[i-3] + score[i-1] + score[i], dp[i-2] + score[i])
  • 정답: dp[n-1]

핵심 요약

✅ DP 적용 조건
   → 최적 부분 구조 + 중복 부분 문제 둘 다 성립

✅ Top-Down vs Bottom-Up
   → Top-Down: 재귀 + 메모이제이션, 직관적
   → Bottom-Up: 반복문 + 테이블, 빠르고 안정적
   → 코테 기본: Bottom-Up 추천

✅ 점화식 도출 순서
   → dp[i]의 의미 정의 → 마지막 선택 분석 → 점화식 작성

✅ 1차원 DP 대표 유형
   → 피보나치: dp[i] = dp[i-1] + dp[i-2]
   → 1로 만들기: dp[i] = min(dp[i//3], dp[i//2], dp[i-1]) + 1
   → 계단: dp[i] = max(dp[i-3]+s[i-1]+s[i], dp[i-2]+s[i])
   → 동전 최소: dp[i] = min(dp[i-coin]+1)
   → 동전 경우수: dp[i] += dp[i-coin]

DP - 언제 뭘 쓸지 정리

  • 최솟값/최댓값을 구해라 + 이전 선택이 영향을 줌 → DP (그리디가 아니라 DP인 이유: 모든 경우 고려 필요)
  • 경우의 수를 구해라+ 부분 문제가 반복됨 → DP (재귀로 짜면 시간초과 → 메모이제이션/테이블)
  • 연속 조건이 있는 최대/최소 → dp[i] 정의 시 마지막 선택을 잘 분류해야 함
  • 점화식이 안 떠오를 때 → dp[1], dp[2], dp[3] 손으로 직접 채워보기 → 패턴이 보이면 일반화

프로그래머스 추천 문제 5개

프로그래머스 피보나치 수 https://school.programmers.co.kr/learn/courses/30/lessons/12945

프로그래머스 N으로 표현 https://school.programmers.co.kr/learn/courses/30/lessons/42895

프로그래머스 정수 삼각형 https://school.programmers.co.kr/learn/courses/30/lessons/43105

프로그래머스 등굣길 https://school.programmers.co.kr/learn/courses/30/lessons/42898

프로그래머스 도둑질 https://school.programmers.co.kr/learn/courses/30/lessons/42897


문제풀이

문제 - 프로그래머스 피보나치 수
https://school.programmers.co.kr/learn/courses/30/lessons/12945

문제 설명

피보나치 수는 F(0) = 0, F(1) = 1일 때, 1 이상의 n에 대하여 F(n) = F(n-1) + F(n-2) 가 적용되는 수 입니다.

예를들어

  • F(2) = F(0) + F(1) = 0 + 1 = 1
  • F(3) = F(1) + F(2) = 1 + 1 = 2
  • F(4) = F(2) + F(3) = 1 + 2 = 3
  • F(5) = F(3) + F(4) = 2 + 3 = 5

와 같이 이어집니다.

2 이상의 n이 입력되었을 때, n번째 피보나치 수를 1234567으로 나눈 나머지를 리턴하는 함수, solution을 완성해 주세요.

제한 사항

  • n은 2 이상 100,000 이하인 자연수입니다.

입출력 예

nreturn
32
55

입출력 예 설명

피보나치수는 0번째부터 0, 1, 1, 2, 3, 5, ... 와 같이 이어집니다.

문제가 잘 안풀린다면😢

힌트가 필요한가요? [코딩테스트 연습 힌트 모음집]으로 오세요! → 클릭

관련 포스트

댓글

방명록을 남겨주세요