public class DynamicProgramming {
	
	// DP
	// 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상시키는 방법
	// 이미 계산된 결과(작은 문제)는 별도의 메모리 영역에 저장하여 다시 계산되지 않도록 한다.
	// 일반적으로 탑다운과 보텀업으로 구성된다. 1. 최적 부분 구조(작은 문제를 모아서 큰 문제 해결 가능), 2. 중복되는 부분 문제(동일한 작은 문제를 반복적으로 해결해야 할 때)
	// 위에 1, 2 조건 모두 만족하면 DP로 풀 수 있다.
	// 탑다운(재귀 함수), 보텀업(반복문)
	// 피보나치 수열을 예로 들 수 있다.
	// 점화식을 만들 수 있는지 확인 필요
	// 피보나치 수열의 점화식 : an = an-1 + an-2, a1 = 1, a2 = 1
	// optimal solution : 최적의 해
	
	public static int[] dpArr1 = new int[100];
	public static int[] dpArr2 = new int[100];
	
	public static int fiboRecursiveFun(int n) { // 1, 1, 2, 3, 5, 8, 13, 21..
		
		if (n == 1 || n == 2) {
			return 1;
		} else {
			return fiboRecursiveFun(n-1) + fiboRecursiveFun(n-2);
				   // fiboRecursiveFun(4) 													+ fiboRecursiveFun(3)
				   // fiboRecursiveFun(3) 							+ fiboRecursiveFun(2) 	+ fiboRecursiveFun(3)
				   // fiboRecursiveFun(2) + fiboRecursiveFun(1) 	+ fiboRecursiveFun(2) 	+ fiboRecursiveFun(3)
				   //        1            +         1               +          1            + fiboRecursiveFun(3)
			 	   //        1            +         1               +          1            + fiboRecursiveFun(2) + fiboRecursiveFun(1)
				   //	     1            +         1               +          1            +          1          +           1
				   // 단순 재귀함수를 이용해 풀게 되면 계산한 것을 또 계산하기 때문에 지수 시간 복잡도를 가지게 된다.
				   // fiboRecursiveFun(5)를 구하기 위해 fiboRecursiveFun(2)가 3번 호출된다. 숫자가 커지면 더 많이 호출될 것이다.
				   // 이미 구한 값은 메모리에 저장해놓고 꺼내서 쓰기만 하면 더 효율적이다.
		}
	}
	
	// 탑다운 방식(하향식)에서 사용되는 메모이제이션(DP를 구현하는 방법 중 하나) 재귀함수를 이용할 거야
	// fiboRecursiveFun에서 구한 값을 저장해놓는 방식
	public static int fiboTopDown(int n) {
		
		if (n == 1 || n == 2) {
			return 1;
		}
		
		if (dpArr1[n] != 0) {
			return dpArr1[n];
		}
		
		dpArr1[n] = fiboTopDown(n - 1) + fiboTopDown(n - 2); // dpArr1[1], dpArr1[2]에는 1이 들어갈 것
		
		return dpArr1[n];
	}
	
	// 보텀업 방식(상향식)은 아래쪽에서부터 작은 문제를 하나씩 해결해 나가면서 다음 문제들을 해결해 나가는 방식으로 반복문을 이용한다.(전형적인 형태)
	// 결과 저장용 리스트는 DP테이블이라고 한다.
	public static int bottomUp(int n) {
		
		dpArr2[1] = 1;
		dpArr2[2] = 1;
		
		for (int i = 3; i <= n; i++) {
			dpArr2[i] = dpArr2[i - 1] + dpArr2[i - 2];
		}
		
		return dpArr2[n];
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(fiboRecursiveFun(5)); // 재귀함수를 이용한 피보나치 // 5
		System.out.println(fiboTopDown(5)); // 재귀함수를 이용한 탑다운 DP // 5
		System.out.println(bottomUp(5)); // 반복문을 이용한 보텀업 DP // 5
		
		// 분할 정복에서는 동일한 부분 문제가 반복적으로 계산되지 않는다.(퀵 정렬이 대표적)
		// 다른 알고리즘으로 풀이 방법이 떠오르지 않으면 다이나믹 프로그래밍을 고려해보자
		// 일단 재귀 함수로 비효율적인 완전 탐색 프로그램을 작성한 뒤에 (탑다운) 작은 문제에서 구한 답이 큰 문제에서 그대로 사용될 수 있으면, 메모이제이션을 활용해 코드를 개선하도록 하자
		// 일반적인 코딩 테스트 수준에서는 기본 유형의 다이나믹 프로그래밍 문제가 출제되는 경우가 많다.
	}
}

DP 알고리즘 문제 풀이 Java

'Algorithm > DP' 카테고리의 다른 글

[Algorithm] DP 이해하기  (0) 2022.11.27

DP(Dynamic Programming)는 완전 탐색, DFS, BFS와 같이 수많은 경우를 따져봐야 할 때

속도가 느려지는 문제를 개선하고자 수행 시간 단축을 목적으로 만들어진 알고리즘이다.

프로그래머스의 정수 삼각형 문제를 예로 들어보자

 

 

 

 

 

 

 

 

 

DP 삼각형의 11이 16으로 갱신되었으므로 이제 11을 만들었던 7-3-1 조합은 고려하지 않아도 된다.

 

 

여기서 DP의 장점을 확인할 수 있다.

DP 삼각형을 활용하면 세 번째 줄의 값을 구할 때

두 번째 줄의 값만 확인하고, 첫 번째 줄의 값은 확인하지 않아도 된다.

왜냐하면 두 번째 줄에는 두 번째 줄까지의 모든 조합들 중 최고의 값만 남겨놨기 때문이다.

(이전의 정보를 확인할 필요가 없게 됨)

 

 

다섯 번째 줄까지의 최댓값은 30이며 이 값은 7-3-8-7-5 조합의 결과이다.

 

 

DP의 목적 : 메모리를 사용해서 중복 연산을 줄이고, 중복 연산을 줄여서 수행 속도를 개선한다.

메모리를 사용한다 : 배열 혹은 자료구조를 만든다.

중복 연산을 줄인다 : 연산한 결과를 배열에 담는다.

 

사실 'Dynamic Programming'보다 '기억하며 풀기 알고리즘'이 더 적합한 명칭인 거 같다.

 

DP 문제인지 확인하는 방법

1. DFS / BFS로 풀 수 있지만 경우의 수가 너무 많은 문제

(최대 경우의 수가 500만 개가 넘어간다면 DP로 풀이하는 게 효율적)

2. 경우의 수들에 중복적인 연산이 많은 경우

 

DP 유형의 경우 오래 붙잡고 있기 보다 30분 정도 고민(어떻게 하면 뒤로 돌아가지 않을 수 있을까) 후

답이 안 나오면 풀이를 참고해서 구현만 해보는 방식이 좋다.

현재 단계까지 연산을 잘 했다면 이 연산을 또 하지 않기 위해 어떤 정보를 어떻게 남겨야 할지 생각해 보자

'Algorithm > DP' 카테고리의 다른 글

[Algorithm] DP-1  (0) 2022.11.27

+ Recent posts