본문 바로가기
CS/알고리즘

동적 계획법 (Dynamic Programming: DP) 이란?

by RuntimeSimple 2025. 2. 12.

 

1️⃣ 동적 계획법(DP)이란?

정의:

  • 동적 계획법(Dynamic Programming, DP)은 큰 문제를 여러 개의 작은 부분 문제로 나누어 그 해를 저장하면서 해결하는 방법입니다. 이를 통해 반복적인 계산을 줄이고, 문제를 효율적으로 해결할 수 있습니다.

핵심 아이디어:

  • 부분 문제의 결과를 저장해서 같은 계산을 반복하지 않는 것이 핵심입니다.
  • DP는 최적화 문제에 주로 사용되며, 주어진 문제를 최적 부분 구조중복되는 부분 문제로 나눌 수 있을 때 사용됩니다.

2️⃣ DP가 필요한 이유는?

일반적인 재귀 방식은 같은 부분 문제를 반복해서 계산하기 때문에 비효율적일 수 있어요. 동적 계획법은 이러한 중복 계산을 제거함으로써 시간 복잡도를 크게 줄일 수 있습니다.

재귀만 사용하는 경우:

  • 피보나치 수열과 같은 문제에서, 같은 값을 여러 번 계산하게 되어 시간이 매우 많이 소요됩니다.

DP를 사용하는 경우:

  • 한 번 계산한 값은 저장해서 이후에 다시 계산할 필요가 없도록 하여 불필요한 계산을 방지합니다.

3️⃣ DP의 핵심 개념

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

  • 큰 문제의 최적 해답이 작은 문제들의 최적 해답으로 구성될 수 있어야 합니다.
  • 예를 들어, 최단 경로를 구할 때
    A에서 B로 가는 최단 경로는
    A에서 중간 경로까지의 최단 경로와
    중간에서 B로 가는 최단 경로로
    나눌 수 있습니다.

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

  • 여러 부분 문제를 해결할 때 같은 문제를 여러 번 반복하여 해결해야 하는 경우가 발생합니다.
  • 예를 들어, 피보나치 수열에서 F(5)를 계산하기 위해 F(4)와 F(3)을 계산하게 되고,
    F(4)를 계산할 때 다시 F(3)을 계산하게 됩니다.
    DP는 이런 중복을 피할 수 있게 합니다.

4️⃣ DP의 구현 방식

DP를 구현하는 두 가지 방식:

 

1) 메모이제이션(Memoization, 탑다운 방식) 📝

"큰 문제를 해결하는 과정에서 작은 문제의 결과를 저장하고 재사용하는 방식"

탑다운(Top-Down) 방식으로, 재귀 호출을 사용하여 해결합니다.
✔ 이미 계산한 값은 배열(메모리)에 저장하고, 동일한 문제가 다시 나타나면 저장된 값을 재사용합니다.

 

📌 탑다운 방식의 동작 과정

a. 큰 문제를 먼저 해결하려고 시도
b. 해결 과정에서 더 작은 부분 문제를 재귀적으로 호출
c. 이미 해결한 문제라면 저장된 값을 반환하여 중복 계산을 방지
d. 처음 등장하는 문제라면 직접 계산하고, 그 결과를 저장 후 반환

 

예제 코드: 피보나치 수열 (메모이제이션 적용)

def fibonacci(n, memo={}):
    if n <= 1:
        return n
    if n not in memo:  # 값이 저장되지 않았다면 계산
        memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
    return memo[n]  # 저장된 값 반환

print(fibonacci(10))  # 55
  • memo 딕셔너리를 활용하여 중복 계산을 방지
  • 재귀 호출을 통해 문제를 분할하여 해결

2) 타뷸레이션(Tabulation, 바텀업 방식) 

"작은 문제부터 해결하면서 점진적으로 큰 문제를 해결하는 방식"

바텀업(Bottom-Up) 방식으로, 반복문을 사용하여 순차적으로 해결합니다.
배열에 값을 미리 저장하면서 작은 문제부터 차례로 큰 문제를 해결합니다.

 

📌 바텀업 방식의 동작 과정

a. 가장 작은 부분 문제부터 시작
b. 해결된 작은 문제의 결과를 활용하여 점점 더 큰 문제를 해결
c. 반복문을 통해 모든 문제를 해결한 후, 최종 값을 반환

 

예제 코드: 피보나치 수열 (타뷸레이션 적용)

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
  • 배열 dp를 활용하여 값을 저장하면서 점진적으로 해결
  • 반복문을 사용하여 재귀 호출 없이 문제를 해결

메모이제이션 vs. 타뷸레이션 비교

 

  메모이제이션(탑다운) 타뷸레이션(바텀업)
접근 방식 큰 문제를 먼저 해결, 작은 문제를 재귀적으로 호출 작은 문제부터 해결하여 큰 문제를 점진적으로 해결
사용 방식 재귀 함수 + 저장 반복문 + 배열
메모리 사용량 함수 호출 스택 + 저장 배열 저장 배열만 사용
속도 재귀 호출로 인해 함수 호출 오버헤드 발생 가능 반복문으로 처리하여 속도가 빠름
적합한 경우 특정 경우에만 필요한 부분 문제를 해결할 때 모든 부분 문제를 한 번에 계산할 때

 

재귀 호출이 많아지는 경우, 스택 오버플로우 위험이 있으므로 바텀업 방식이 유리
반면, 불필요한 부분 문제를 계산하지 않고 넘어갈 수 있는 경우에는 메모이제이션이 유리


결론: 언제 어떤 방식을 선택해야 할까?

메모이제이션 (탑다운) 방식이 적합한 경우

  • 부분 문제 중 불필요한 계산을 줄일 필요가 있는 경우
  • 특정 부분 문제만 필요할 때
  • 재귀 호출이 가능한 경우

타뷸레이션 (바텀업) 방식이 적합한 경우

  • 모든 부분 문제를 한 번씩 계산해야 하는 경우
  • 재귀 호출이 너무 깊어지는 경우 (스택 오버플로우 방지)
  • 반복문을 활용하는 것이 더 직관적이고 효율적인 경우

=> 한 줄 요약:

  • "부분 문제를 선택적으로 계산해야 한다면 → 메모이제이션(탑다운)"
  • "모든 부분 문제를 계산해야 한다면 → 타뷸레이션(바텀업)"

5️⃣ DP를 적용할 수 있는 문제들

동적 계획법은 다양한 최적화 문제에서 자주 사용되며, 대표적인 문제 유형은 다음과 같습니다:

1. 피보나치 수열 문제

  • 피보나치 수열을 구하는 문제는 동일한 부분 문제를 여러 번 반복해서 계산해야 하므로, DP로 해결할 수 있습니다.

2. 최단 경로 문제

  • 다익스트라 알고리즘이나 플로이드-와샬 알고리즘에서 DP를 사용최단 경로를 구할 수 있습니다.

3. 배낭 문제 (Knapsack Problem)

  • 물건을 배낭에 담아 최대 가치를 얻는 문제에서, DP는 부분 문제를 저장하고 재사용해 최적 해답을 구할 수 있습니다.

4. 최소 동전 거스름돈 문제

  • 주어진 동전 종류로 특정 금액을 맞추는 문제에서, DP를 사용해 최소 동전 개수를 찾을 수 있습니다.

5. 최장 공통 부분 수열 (Longest Common Subsequence, LCS)

  • 두 문자열에서 공통된 부분 문자열을 찾는 문제에서도 DP를 사용해 효율적으로 해답을 구할 수 있습니다.

💡 DP를 잘 사용하기 위한 팁

  1. 문제 분해: 문제를 작은 부분 문제로 나누고, 그 해답을 저장하는 구조로 바꿔 보세요.
  2. 메모이제이션 활용: 중복되는 부분 문제를 찾아 한 번 계산한 값을 재사용하면 성능이 크게 향상됩니다.
  3. 작은 문제부터 해결( 타뷸레이션 ): 큰 문제를 해결하기 위해 작은 문제부터 해결하는 방법(바텀업 방식)이 더 효율적일 때도 있습니다.

🎯 DP를 사용하는 기준

동적 계획법을 적용할 수 있는 문제인지 확인하는 기준은 두 가지입니다:

  • 최적 부분 구조: 큰 문제를 작은 문제로 나누어 해결할 수 있나요?
  • 중복되는 부분 문제: 작은 문제를 여러 번 반복해서 계산하게 되나요?

DP를 적용하려면 반드시 "최적 부분 구조"와 "중복되는 부분 문제"가 모두 성립해야 합니다!


📌 정리

개념 설명
동적 계획법 (DP) 큰 문제를 작은 부분 문제로 나누어 해결하고, 중복된 계산을 피하기 위해 그 결과를 저장해 최적의 해를 찾는 방법
최적 부분 구조 큰 문제의 해답이 작은 문제의 해답으로 구성될 수 있는 구조
중복되는 부분 문제 같은 문제를 반복해서 계산해야 하는 상황
메모이제이션 계산한 결과를 저장해, 같은 계산을 반복하지 않도록 하는 방식 (탑다운 방식)
타뷸레이션 작은 문제부터 차례대로 해결하는 방식 (바텀업 방식)