코딩 테스트(Coding Test)/알고리즘 개념

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

배씌 2024. 12. 4. 17:54

점화식

인접한 항들 사이의 관계식.

 

각 항을 an 이라 할때, 피보나치 수열의 점화식은 다음과 같이 표현할 수 있다.

an+2 = f(an+1, an) = an+1 + an

 

결과적으로 피보나치 수열에서는 첫 번째 항과 두 번째 항의 값이 모두 1이기 때문에 아래처럼 나타낸다.

an = an-1 + an-2, a1 = 1, a2 = 1

  • n번째 피보나치 수 = (n - 1)번째 피보나치 수 + (n - 2)번째 피보나치 수
  • 단, 1번째 피보나치 수 = 1, 2번째 피보나치 수 = 1

점화식을 프로그래밍으로 표현하려면 재귀 함수를 사용한다.

 

- 피보나치 함수

static int fibo(int n) {
    if (n == 1 || n == 2) return 1;
    return fibo(n - 1) + fibo(n - 2);
}

 

그러나 위 방법은 O(2N) 이라는 심각한 시간복잡도를 기록한다.

 

그림을 보면 동일한 F(3)이 반복해서  호출된다. 이는 n이 커지면 커질수록 반복해서 호출되는 함수가 많아짐을 의미한다.

 

 

 

 

 

 

방법 1) Top - Down (재귀적)

메모이제이션(Memoization) : 다이나믹 프로그래밍을 구현하는 방법 중 하나. 한번 구한 결과를 메모리 공간에 메모해두고 같은 식을 다시 호출하면 메모한 결과를 그대로 가져온다. -> 리스트, 배열에 저장

static int[] d = new int[100];  // 메모이제이션 배열

// 피보나치 수열 - Top Down
static int fibo(int n) {
    // 종료 조건 (1 or 2일때 1반환)
    if(n == 1 || n == 2) return 1;
    // 이미 계산한 적 있는 문제라면 그대로 반환
    if(d[n] != 0) return d[n];
    // 아직 계산하지 않은 문제라면 점화식에 따라서 피보나치 결과 반환
    d[n] = fibo(n - 1) + fibo(n - 2);
    return d[n];
}

 

위 메모이제이션에서는 아래 그림처럼 F(6)을 호출하면 한 번 푼 문제는 그 결과를 이용하기 때문에 아래 노드만 방문한다.

DP 방식 시간복잡도 : O(N)

 

방법 2) Bottom - Up (반복문)

int[] d = new int[100]; // DP 테이블

// 첫 번째 피보나치 수와 두 번째 피보나치 수는 1
d[1] = 1;
d[2] = 2;

// 피보나치 함수 반복문으로 구현 (Bottom Up)
for(int i=3; i<100; i++) {
    d[i] = d[i-1] + d[i-2];
}

System.out.println(d[99]);

다이나믹 프로그래밍

  • 탑다운(Top Down) : 재귀함수 (메모이제이션)
  • 바텀업(Bottom Up) : 반복문 (DP 테이블)

가능하다면 재귀함수를 이용하는 탑다운 방식보다는 바텀업 방식으로 구현하자.