𝝅번째 알파카의 개발 낙서장

screen

[백준 / JAVA] 백준 알고리즘 1003번 피보나치 함수

posts

알고리즘

시리즈 톺아보기

백준 알고리즘

백준 알고리즘
count

피보나치 함수 🔗

랭크 사용 언어
image JAVA

🔗 전체 1003번 문제

조건 🔗

시간제한 메모리 제한
0.25초 (추가 시간 없음) 128MB

문제 🔗

다음 소스는 N번째 피보나치 수를 구하는 C++ 함수이다.

CPP

0int fibonacci(int n) {
1 if (n == 0) {
2 printf("0");
3 return 0;
4 } else if (n == 1) {
5 printf("1");
6 return 1;
7 } else {
8 return fibonacci(n‐1) + fibonacci(n‐2);
9 }
10}

을 호출하면 다음과 같은 일이 일어난다.

  • (첫 번째 호출)을 호출한다.
  • (두 번째 호출)과 을 호출한다.
  • 두 번째 호출한 은 1을 출력하고 1을 리턴한다.
  • 은 0을 출력하고 0을 리턴한다.
  • 의 결과를 얻고, 1을 리턴한다.
  • 첫 번째 호출한 은 1을 출력하고, 1을 리턴한다.
  • 의 결과를 얻고, 2를 리턴한다.

1은 2번 출력되고, 0은 1번 출력된다. N이 주어졌을 때, 을 호출했을 때, 0과 1이 각각 몇 번 출력되는지 구하는 프로그램을 적성하시오.

입력 🔗

첫째 줄에 테스트 케이스의 개수 T가 주어진다.
각 테스트 케이스는 한 줄로 이루어져 있고, N이 주어진다. N은 40보다 작거나 같은 자연수 또는 0이다.

출력 🔗

각 테스트 케이스마다 0이 출력되는 횟수와 1이 출력되는 횟수를 공백으로 구분해서 출력한다.

케이스 🔗

  • 입력

TC

03
10
21
33
  • 출력

TC

01 0
10 1
21 2

풀이 🔗

알고리즘 풀면서 느끼는 거지만, 문제가 뭘 말하는 지 이해가 안 되는 경우가 많은 것 같다. 내가 멍청해서 그런건가.

이 문제를 풀기 위해선, 피보나치 수열에 대한 식을 이해하고 있어야 한다.
피보나치 수열이 일 때, 번째 피보나치 수열의 식은 로 정의할 수 있다.

일 때의 초기값이 정해져있다. (식의 특성 상 초기값이 없으면 계산할 수가 없다.)


초기값은 위와 같으며, 실질적으로 부터 의미있는 연산이 수행된다.

다시 문제로 돌아가서, 임의의 수 N이 주어질 경우 을 수행하면서 , 이 몇 번 호출되는지를 구하면 된다.
예를 들어, 라고 가정하고 식을 전개하면 아래와 같다.

위 식에서 로 치환할 수 있으며, 같은 이유로 역시 으로 치환 가능하다.

결과적으로 로 정리할 수 있다.
따라서 이 문제의 알고리즘은 일 경우 2 3이 출력되어야 한다.

우선 식을 정리하여 한 눈에 보면 문제 해결에 도움이 될 것 같다.
피보나치 수열을 쭉 정리하면 아래와 같다.

의 갯수 의 갯수
0 1 0 0
1 0 1 1
2 1 1 1
3 1 2 2
4 2 3 3
5 3 5 5
6 5 8 8
7 8 13 13
8 13 21 21
9 21 34 34

표로 정리하니 어느정도 규칙성이 눈에 보이기 시작한다.

  • N의 출력 갯수는 과 동일하다.
  • N의 출력 갯수는 과 동일하다.

즉, 일 경우 알고리즘은 가 출력되면 된다.

여기서 단순하게 생각하면 아래와 같이 코드를 짤 수 있다.

완성....? 🔗

JAVA

0import java.util.Scanner;
1
2/**
3 * 백준 전체 1003 문제 알고리즘 클래스
4 *
5 * @author RWB
6 * @since 2021.04.21 Wed 23:29:03
7 */
8public class Main
9{
10 static Integer[][] arr = new Integer[41][2];
11
12 /**
13 * 메인 함수
14 *
15 * @param args: [String[]] 매개변수
16 */
17 public static void main(String[] args)
18 {
19 Scanner scanner = new Scanner(System.in);
20
21 // N = 0일 때, 0이 호출되는 횟수
22 arr[0][0] = 1;
23
24 // N = 0일 때, 1이 호출되는 횟수
25 arr[0][1] = 0;
26
27 // N = 1일 때, 0이 호출되는 횟수
28 arr[1][0] = 0;
29
30 // N = 1일 때, 1이 호출되는 횟수
31 arr[1][1] = 1;
32
33 int length = scanner.nextInt();
34
35 for (int i = 0; i < length; i++)
36 {
37 int n = scanner.nextInt();
38
39 int f0 = fibonacci(n - 1);
40 int f1 = fibonacci(n);
41
42 System.out.println(f0 + " " + f1);
43 }
44 }
45
46 /**
47 * 피보나치 값 반환 함수
48 *
49 * @param n: [int] 인덱스
50 *
51 * @return [int] 피보나치 값
52 */
53 private static int fibonacci(int n)
54 {
55 // 인덱스가 0일 경우
56 if (n == 0)
57 {
58 return 0;
59 }
60
61 // 인덱스가 1일 경우
62 else if (n == 1)
63 {
64 return 1;
65 }
66
67 // 인덱스가 2 이상일 경우 (연산 가능)
68 else
69 {
70 return fibonacci(n - 1) + fibonacci(n - 2);
71 }
72 }
73}

위 코드는 크게 두 가지 문제가 있는데, 우선 일 때의 처리가 정상적으로 이루어지지 않고 있다.

N이 문제되기 이전에 이 코드는 런타임 시간 초과로 실패한다. 왜일까?

위 코드는 불필요한 연산을 너무 많이 수행한다. 피보나치 수열의 특성 상 을 계산할 경우, , ... 등과 같이 N 이하의 피보나치 값까지 전부 계산하게 된다.
다시 말하면, 을 연산할 경우 계산 과정에서 자연스레 , 등의 피보나치 값을 구할 수 있다.

위 이론을 알고리즘에 적용하면 아래와 같이 응용할 수 있다.
N을 총 3번 입력한다고 가정하면 , , 으로 구분할 수 있다.

-> 부터 까지의 값을 구할 수 있음.
-> 부터 까지의 값을 구할 수 있음.

피보나치 연산값을 저장하면 일 경우 굳이 추가적인 연산을 진행하지 않고 이미 저장된 값을 출력만 함으로써, 런타임 리소스를 줄일 수 있다.

클래스의 멤버변수로 Integer 배열을 선언하여 피보나치 수열값을 저장하고, 알고리즘 연산에 이를 활용하면 될 것 같다.

int는 Primitive(자료형) 데이터고, Integer는 Wrapper 클래스다. Wrapper 클래스는 null 입력이 가능하다는 특징이 있으므로, Integer 역시 숫자 이외에 null값을 입력할 수 있다.
Integer 배열의 초기값은 null로 지정되므로, 배열의 값이 null인 인덱스는 아직 피보나치 수열 계산이 이루어지지 않은 인덱스라고 판단할 수 있다.

다행히 문제에서 주어진 의 조건은 이므로, 배열의 인덱스는 최대 41을 넘지 않음을 알 수 있다.
(배열은 0부터 시작하므로 40개가 아닌 0을 포함한 41개임에 유의하자)

이후 피보나치 연산에서 각 과정의 값을 배열에 저장하는 로직을 추가한다.
배열의 값이 null일 경우, 아직 연산이 진행되지 않았으므로 피보나치 연산을 수행하고 배열에 저장한다.
반대로, 배열이 특정 숫자값을 가질 경우, 이미 연산이 진행된 인덱스이므로 별도의 연산을 거치지 않고 해당 값을 바로 출력한다.

전체 소스 🔗

JAVA

0import java.util.Scanner;
1
2/**
3 * 백준 전체 1003 문제 알고리즘 클래스
4 *
5 * @author RWB
6 * @see <a href="https://blog.itcode.dev/posts/2021/05/21/a1003">1003 풀이</a>
7 * @since 2021.04.21 Wed 23:29:03
8 */
9public class Main
10{
11 static Integer[] arr = new Integer[41];
12
13 /**
14 * 메인 함수
15 *
16 * @param args: [String[]] 매개변수
17 */
18 public static void main(String[] args)
19 {
20 Scanner scanner = new Scanner(System.in);
21
22 // 피보나치 수열 초기값 (N = 0)
23 arr[0] = 0;
24
25 // 피보나치 수열 초기값 (N = 1)
26 arr[1] = 1;
27
28 int length = scanner.nextInt();
29
30 for (int i = 0; i < length; i++)
31 {
32 int n = scanner.nextInt();
33
34 fibonacci(n);
35
36 // n이 0일 경우
37 if (n == 0)
38 {
39 System.out.println("1 0");
40 }
41
42 // n이 1일 경우
43 else if (n == 1)
44 {
45 System.out.println("0 1");
46 }
47
48 // 초기값이 아닐 경우
49 else
50 {
51 System.out.println(new StringBuffer().append(arr[n - 1]).append(" ").append(arr[n]).toString());
52 }
53 }
54
55 scanner.close();
56 }
57
58 /**
59 * 피보나치 값 반환 함수
60 *
61 * @param n: [int] 인덱스
62 *
63 * @return [int] 피보나치 값
64 */
65 private static int fibonacci(int n)
66 {
67 // 해당 인덱스의 피보나치가 아직 연산되지 않았을 경우
68 if (arr[n] == null)
69 {
70 arr[n] = fibonacci(n - 1) + fibonacci(n - 2);
71 }
72
73 return arr[n];
74 }
75}

분류 🔗

  • 다이나믹 프로그래밍