JS/알고리즘

JS, DP(Dynamic Programming)

Hyeon_E 2023. 12. 13. 20:41

[ 동적 계획법(Dynamic Programming) ]

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

즉 큰 문제를 작은 문제로 쪼개서 그 답을 저장해두고 재활용 함

 

▶ DP를 사용하는 이유

일반적인 재귀를 단순히 사용 시 동일한 작은 문제들이 여러번 반복되어 비효율적인 계산이 될수 있음

예를 들어 피보나치 수의 경우 1이상의 n의 경우 F(n) = F(n-2) + F(n-1)적용되는 수를 말함(F(0)=0, F(1)=1)

 

만약 n이 4라면 재귀호출 방식을 사용하게 되면

F(4) = F2 + F(3) = (F(0) + F(1) ) + ( F(1) + F(2) ) = (F(0) + F(1) ) + ( F(1) + (F(0) + F(1) ) )

중복된 값을 다시 구하게 되는 경우가 발생 따라서 n이 커짐에 따라  시간 초과나 런타임 에러가 남

 

따라서 단순히 재귀호출을 하는 대신 1, 2, 3, ..., n번째 피보나치 수를 순서대로 구하고 작은 문제의 답을 저장해서 사용

즉 DP의 핵심은 겹치는 부분 문제의 답을 저장해 재활용 한다는 것

 

▶ DP 구현방법

Top-Down 방식

큰 문제부터 문제를 쪼개가며 작은 문제로 만들고 다시 합쳐가며 원래 문제를 푸는 방식

주로 재귀함수를 사용함

let dp = [0, 1];
function solution(n) {
  if (!dp[n]) { dp[n] = solution(n - 1) + solution(n - 2);
  // 저장한 값이 없다면 재귀를 이용해 구하고 저장
  return solution[n];
}

 

Bottom-Up 방식

작은 문제들을 모아서 큰 문제를 만들어 쌓아 올려가는 방식

주로 for문을 사용함

function solution(n) {
    let dp = [0, 1]
    for(let i=3; i<=n; i++) dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]
}

 

 

Tip: 나머지 연산의 성질

코테를 풀다보면 언어가 표현할 수 있는 자료형의 범위를 넘어가 오버플로우가 남

문제를 잘 살펴보면 1234567으로 나눈 나머지를 리턴하는 함수등 나머지를 이용하게끔 문제가 나옴

나머지 연산의 성질을 이용하여 모든 단계에서 %연산을 사용해 오버플로우가 일어나지 않게 만들 수 있음

(a + b) % m = ((a % m) + (b % m)) % m

 

F(n) % m 
= (F(n-1) + F(n-2)) % m
= (F(n-1) % m + F(n-2) % m) % m

 

- 나머지 연산의 성질을 이용한 피보나치수 문제

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

function solution(n) {
    let arr = [0, 1]
    for(let i=2; i<=n; i++) arr[i] = (arr[i - 2] + arr[i - 1]) % 1234567
    return arr[n]
}