기본개발지식

[알고리즘] 동적계획법 DP(Dynamic Programming)

석태 2024. 1. 29. 17:12

동적계획법 DP를 알기전에

  • optimization problem이란?
- 문제를 해결하는 최적의 답(optimal solution)을 찾아야 하는 문제
- optimal solution은 하나 이사일 수 있다.
- maximum 혹은 minimum value를 가지는 solution을 찾는 문제들이 주를 이룬다.
ex) 가장 빨리 도착하는 경로의 소요 시간은? 또는 언제 주식을 사고 팔 때 가장 수익이 높은지?
value : 소요시간, 수익
solution : 경로, 언제 주식을 사고 팔때
동적계획법 DP(Dynamic Programming)이란
- optimization problem을 해결하는 전략 중 하나
- subproblem(s)의 optimal solution(s)을 활용해서 problem의 optimal solution을 찾는 방식
- 계속 쪼개다보면 겹치는(overlapping) subproblems은 한번만 계산하고 그 결과를 저장한 뒤 재사용한다.
작은 문제들을 풀면서 그 결과를 저장해 나아가면서 전체 문제를 해결하는 알고리즘을 의미한다.
특정 범위까지의 최적해를 구하기 위하여 다른 범위까지의 최적해를 이용하여 효율적으로 해를 구하는 알고리즘 설계 기법
→ 이전에 구한 값을 기반으로 규칙성을 파악하여 다음 값을 구하는 것
→ 쉽게 말하면 하나의 큰 문제를 여러 개의 작은 문제로 나우어 그 결과를 저장하여 다시 큰 문제를 해결할 때 사용하는 것

 

 

DP를 사용하는 이유

재귀함수와 방식이 유사하다.

재귀함수 : 단순히 사용할 때 작은 문제 하나에서 작은 문제가 있다면 계속 반복하여야 하기 때문에 규모가 큰 계산은 비효율적이다.

분할정복(Divide and Conquer) : 분할 정복은 문제를 분할했을 떄 겹치는 문제가 발생하지 않는다.

중복 계산을 피하면서 최적 부분 구조를 활용하여 문제를 해결하는 효율적인 알고리즘 설계 기법 중 하나이다.

 

 

DP의 두 가지 접근 방식
  Top-Down approach Bottom-Up approach
구조 recursive iterative(for-loop)
subproblem 결과 저장 memoization
(function call 결과를 저장해서 이후에 같은 input에 대한 호출은 저장한 결과값을 사용하는 것)
tabulation
(작은 subproblem부터 시작해서 최종 problem 결과를 도출할 때까지 결과를 차례대로 기록하는 것)
언제 선호하는지 subproblems의 일부만 계산해도 최종 optimal solution을 구할 수 있을 때 모든 subproblems을 계산해야 최종 optimal solution을 구할 수 있을 때
  • Top-Down : 해결하기 위해서 좀더 작은 크기 문제로 해결해보자 
  • Bottom-up : 일단 작은 것부터 해결해 나가자(빌드업) => 알고리즘 문제에서 많이 사용하는 편

 

DP를 사용한 알고리즘 설계 순서
  1. 주어진 문제의 optimal solution이 구조적으로 어떤 특징을 가지는지 분석한다.
  2. 재귀적인 형태로 optiomal solution의 value를 정의한다.
  3. (주로) Bottom-up 방식으로 optimal solution의 value를 구한다.
  4. 지금까지 계산된 정보를 바탕으로 optimal solution을 구한다.

 

예제 1
정상까지 오르는데 n번의 스텝이 필요한 계단이 있다. 한 번에 한 스텝 혹은 두 스텝을 오를 수 있을 때, 계단의 정상까지 오르기 위해 몇 개의 유니크한 방법이 있는지 구하시오(단 n>=1)
example 1: n=2 → 답 : 2 (1 +1 , 2)
example 2: n=3 -> 답 : 3 (1+1+1, 1+2, 2+1)

문제 분석 : 총 몇 개의 유니크한 방법이 있는지를 구하는 것 → 최대 몇개의 방법이 있는지 물어보는 것

경로의 수 f(n) = n스텝의 계단을 오르는 유니크한 방법 수 [ f(n) =f(n-1) + f(n-2) ]

겹치는 subproblem들이 많을 경우 동일한 input에 대한 메소드를 처음 한번만 계산하고 결과를 메모해 뒀다가 이후에 재사용하는 방식으로 해결

 

DP를 적용하기 위한 2가지 조건
  • 부분 반복 문제(Overlapping Subproblem) : 
  • 최적 부분 구조(Optimal Substructure)

 

DP를 사용해야 한다고 판단하는 기준
  • DFS/BFS로 사용 가능하지만 경우의 수가 너무 많은 경우
  • 경우의 수들에 중복적인 연산이 많은 경우
  • DP를 이용할 때 뒤로 돌아가지 않을 수 있는 방법을 고민해보고 연산을 또 하지 않을려면 어떻게 구현해야 하는지 고민을 해야한다.
  • 수행시간을 단축하기 위한 기법이지 알고리즘은 아니다.

 

프로그레머스 동적계획법 문제 정수 삼각형

 

더보기
문제
위와 같은 삼각형의 꼭대기에서 바닥까지 이어지는 경로 중, 거쳐간 숫자의 합이 가장 큰 경우를 찾아보려고 합니다. 아래 칸으로 이동할 때는 대각선 방향으로 한 칸 오른쪽 또는 왼쪽으로만 이동 가능합니다. 예를 들어 3에서는 그 아래칸의 8 또는 1로만 이동이 가능합니다.
삼각형의 정보가 담긴 배열 triangle이 매개변수로 주어질 때, 거쳐간 숫자의 최댓값을 return 하도록 solution 함수를 완성하세요.

 

접근 방법
7         7        
3 8       10 15      
8 1 0     18 16 15    
2 7 4 4   20 25 20 19  
4 5 2 6 5 24 30 27 26 24
  • 삼각형으로 되어 있는 숫자를 2차원 배열 형태로 표현한다.
  • 아래와 오른쪽 대각선 아래로 계산이 되기 때문에 계산을 하여 아래로 숫자를 계산하여 2번 계산이 필요한 중간 부분은 가장 계산에서 큰 숫자를 입력한다.
  • f(n)=f(n-1)+f(n-2)이므로 계속해서 계산할 때 메소드를 호출하지 않도록 dp[][] 2차원 배열을 만들어서 계산한 값을 저장해둔다.
  • 마지막 열에최종 계산된 모든 수가 존재하는데 해당 숫자 중에서 가장 큰 숫자를 반환한다.

 

코드
import java.util.*;
class Solution {
    public int solution(int[][] triangle) {    
        int[][] dp = new int[triangle.length][triangle.length];

        dp[0][0] = triangle[0][0];
        int answer = 0;
        for(int i=0; i<triangle.length-1; i++){
            for(int j=0; j<triangle[i].length; j++){
                dp[i+1][j] = Math.max(dp[i][j]+triangle[i+1][j],dp[i+1][j]);
                dp[i+1][j+1] = Math.max(dp[i+1][j+1],triangle[i+1][j+1]+dp[i][j]);
            }
        }
        answer = Arrays.stream(dp[dp.length-1]).max().getAsInt();
        return answer;
    }

}