문제 설명
피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다.
이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n ≥ 2)가 된다.
n=17일때 까지 피보나치 수를 써보면 다음과 같다.
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597
n이 주어졌을 때, n번째 피보나치 수를 구하는 프로그램을 작성하시오.
입력 )
첫째 줄에 n이 주어진다. n은 90보다 작거나 같은 자연수다.
출력 )
첫째 줄에 n번째 피보나치 수를 출력한다.
위와 동일한 문제이다. 근데 왜 2747은 브론즈2이고 2748은 1인걸까
문제 풀이
피보나치 수열의 점화식은 $a_{n+2}\ =f(a_{n+1},\ a_n)= a_{n+1}+\ a_n$ 으로 표현할 수 있다.
이 점화식 풀면, $a_n=a_{n-1}+a_{n-2}$로 나타낸다.
import java.util.Scanner;
public class Main {
public static int fibo(int n){
if (n == 0 || n == 1) {
return n;
}
return fibo(n - 1) + fibo(n - 2);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(fibo(n));
}
}
fibo 한 번 호출에 두 번의 fibo 함수가 호출되면서 계산이 진행되므로 시간 복잡도는 $O(2^N)$이다.
시간 복잡도가 $O(2^N)$이므로, 계산된 값을 다른 값을 구하기 위해 또 반복하게 되어 N의 값이 커질 수록 문제가 된다. 따라서 위의 코드도 시간 초과이다.
따라서, 메모이제이션을 통해 이미 계산된 결과값에 대해서는 계산이 이루어지지 않도록 한다.
메모이제이션
메모이제이션(Memoization)이란, 한 번 해결한 결과값을 메모리 공간에 저장해둔 후, 같은 식이 호출될 때 결과를 다시 꺼내오는 기법이다. 이 방법은 Top-Down 방식으로, 큰 문제를 해결하기 위해서 작은 문제를 호출하며 풀어나가는 방식이다.
✅[ CODE ]
import java.io.*;
public class Main {
static long[] arr; // 메모이제이션을 위한 값 저장소
public static long fibo(int n){
if (n == 0 || n == 1) {
return n;
}
if (arr[n] != 0) { // 계산된 적이 있다면, 계산을 다시 하지 않는다.
return arr[n];
}
arr[n] = fibo(n - 1) + fibo(n - 2);
return arr[n];
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
arr = new long[n + 1];
// for (int i = 0; i < n + 1; i++) {
// arr[i] = 0;
// }
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write(String.valueOf(fibo(n)));
bw.flush();
br.close();
bw.close();
}
}
long 타입인 arr은 한 번 계산된 값을 저장하기 위한 저장소이다. 0으로 모두 초기화 해준다.
한 번 계산된 값은 다시 계산하지 않고, 계산되지 않은 값만 재귀함수를 통해 계산한다.
계산된 적 없는 값만 계산하기 때문에, 시간 복잡도는 $O(N)$이다.
재귀 함수는 호출될 때마다 메모리 스택에 쌓이게 되는데, 많은 양의 함수 호출로 인해 메모리가 부족할 수 있다.
반복문
반복문은 Bottom-Up 방식 중 하나로, 바텀업 방식은 작은 문제부터 해결하여 큰 문제를 해결하는 방식이다.
✅[ CODE ]
static long fibo(int n) {
if (n == 0 || n == 1) {
return n;
} else {
arr[0] = 0;
arr[1] = 1;
for (int i = 2; i < n + 1; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[n];
}
}
✅[ CODE ]
n + 1의 크기인 배열을 만들어 주지 않고 두 개의 수만 담아서 계산해주는 방법도 있다.
static long fibo(int n) {
long[] arr = {0, 1};
long answer = 0;
if (n == 0 || n == 1) {
return n;
}
for (int i = 2; i < n + 1; i++) {
answer = arr[0] + arr[1];
arr[0] = arr[1];
arr[1] = answer;
}
return answer;
}
두 수만 담아지는 arr의 0번째 인덱스에는 1번째 인덱스 값을 넣어주고,
1번째 인덱스에는 합한 값을 넣어주는 것을 반복한다.