피보나치 수열의 i번째 값을 계산하는 F를 정의하면
F(0) = 0, F(1) = 1
F(i) = F(i-1) + F(i-2) for i >= 2 라고 할 수 있다.
이는 재귀 함수로 구현할 수 있다.
f(n+2) = f(n) + f(n+1)
→ f의 0 번째와, 1번째는 알고 있어야 문제를 풀어갈 수 있다.
public class 피보나치 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
System.out.println(fibo(N));
}
public static int fibo(int n) {
if(n < 2 ) return n;
return fibo(n-1) + fibo(n-2);
}
}
필요 조건
분할 정복과 동적 계획법의 비교
구분 | 분할 정보 | DP |
---|---|---|
문제 해결 방식 | - 연관 없는 부분 문제로 분할한다. | - 부분 문제들이 연관이 없으면 적용할 수 없다. |
- 부분문제를 재귀적으로 해결한다. | - 모든 부분 문제를 한번만 계산하고, 결과를 저장하고 재사용한다. | |
- 부분문제의 해를 결합(Combine)한다. | - (분할 정보와 같은 부분 문제가 나타날 경우 다시 계산한다.) | |
예시 | - 병합정렬 / 퀵정렬 | - 피보나치 수열, 최장 공통 부분 수열 등 |
접근방법 | - 하향식 방법 | - 상향식 방법 |
자바 구현
public static int fibo3(int n) { // 피보나치
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
실행 속도가 빠르다
예시1 > 동전 거스름돈
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int money = sc.nextInt();
int[] dp = new int[money+1];
// 동전의 종류가 1, 4, 6일 때, 최소 개수로 거스름돈을 줄 수 있는 방법
for(int i = 1; i <= money; i++) {
int min = Integer.MAX_VALUE;
min = Math.min(min, dp[i-1]+1); // 거스름돈이 1원인 경우
if(i >= 4) min = Math.min(min, dp[i-4]+1); // 거스름돈이 4원인 경우
if(i >= 6) min = Math.min(min, dp[i-6]+1); // 거스름돈이 6원인 경우
dp[i] = min;
}
System.out.println(dp[money]);
}
예시2 > 가방 챙기기
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt(); // 물건의 개수
int W = sc.nextInt(); // 배낭의 무게
int[] weight = new int[N+1];
int[] cost = new int[N+1];
for(int i = 1; i <= N; i++) {
weight[i] = sc.nextInt();
cost[i] = sc.nextInt();
}
int[][] dp = new int[N+1][W+1];
for(int i = 1; i <= N; i++) {
// 각 종류의 물건은 하나씩만 담을 수 있다는 조건
for(int w = 0; w <= w; w++) {
// w는 임시 무게가 된다.
// 고려할 물건의 무게가 임시 무게보다 작다면
if(weight[i] <= w) {
dp[i][w] = Math.max(dp[i-1][w], dp[i-1][w-weight[i]+cost[i]]);
} else {
dp[i][w] = dp[i-1][w];
}
}
}
System.out.println(dp[N][W]);
}