JAVASCRIPT/문법

[자료구조/알고리즘] 동적계획법(DP)

예글 2024. 3. 10. 16:09

프로그래머스에서 피보나치 수열 코딩테스트 문제를 풀다가...

신나게 재귀함수로 풀어야지~~ 하고 풀었지만 시간초과, 런타임 에러...

프로그래머스에서 제공하는 코딩테스트 연습 힌트 모음집을 보니까 딱 나의 케이스에 대한 내용이 있었다🤣


 

일반적인 재귀를 단순히 사용 시 동일한 작은 문제들이 여러 번 반복 되어 비효율적인 계산이 되어 시간초과가 난 것이었다,,

return f(n) = f(n-1) + f(n-2)

 

이렇게 하게 되면 f(n-1)에서 한 번 구한 값을 f(n-2)에서 또 같은 값을 구하는 과정을 반복하게 되기 때문에 100번째 피보나치 수를 구하기 위해 호출되는 함수의 횟수는 기하급수적으로 증가하는 것이다.

 

그러나 한 번 구한 작은 문제의 결과 값을 저장해두고 재사용하게 되면 앞에서 계산된 값을 다시 반복할 필요가 없이 적은 수의 반복만으로도 계산이 가능해진다.

 

이 참에 DP에 대해 학습해야겠다..!


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

  • 하나의 큰 문제를 여러 개의 작은 문제로 나누어서 그 결과를 저장하여 다시 큰 문제를 해결할 때 사용하는 것

2. DP의 사용 조건

1) Overlapping Subproblems (겹치는 부분 문제)

  • 동일한 작은 문제들이 반복하여 나타나는 경우에 사용이 가능

2) Optimal Substructure (최적 부분 구조)

  • 부분 문제에서 구한 최적 결과가 전체 문제에서도 동일하게 적용되어 결과가 변하지 않을 때 사용가능

3. DP 사용하기

1) DP로 풀 수 있는 문제인지 확인

2) 문제의 변수 파악

3) 변수 간 관계식 만들기 (점화식)

4) 메모하기 (memoization or tabulation)

5) 기저 상태 파악하기

6) 구현하기

4. 구현방법

1) Bottom-Up 방식 (Tabulation)

  • 아래에서부터 계산을 수행하고 누적시켜서 전체 큰 문제를 해결
function solution(n) {
  if (n === 1 || n === 2) return n;
  const dp = Array(n).fill(0);
  const mod = 1000000007;
  dp[0] = 1;
  dp[1] = 2;
  for (let i = 2; i < n; i++) dp[i] = (dp[i - 1] + dp[i - 2]) % mod;
  return dp[n - 1];
}
  • 연산 결과를 저장할 1차원 배열 dp의 길이 n으로 설정, 0으로 초기화
  • n = 1일 때 경우의 수인 1과 n = 2일 때 경우의 수인 2를 배열의 가장 처음에 설정

2) Top-Down 방식

  • 위에서부터 바로 호출을 시작하여 제일 밑 상태까지 내려간 다음 해당 결과 값을 재귀를 통해 전이시켜 재활용하는 방식
function solution(n) {
  if (n === 1 || n === 2) return n;
  const memo = Array(n + 1).fill(0);
  const mod = 1000000007;

  const dp = (n) => {
    if (n <= 2) return n;
    if (memo[n] > 0) return memo[n];
    memo[n] = (dp(n - 1) + dp(n - 2)) % mod;
    return memo[n];
  };
  return dp(n);
}
  • 재귀함수를 사용해서 구현 --> 효율성 테스트 실패