프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
문제 설명
문제 설명
피보나치 수는 F(0) = 0, F(1) = 1일 때, 1 이상의 n에 대하여 F(n) = F(n-1) + F(n-2) 가 적용되는 수 입니다.
예를들어
F(2) = F(0) + F(1) = 0 + 1 = 1
F(3) = F(1) + F(2) = 1 + 1 = 2
F(4) = F(2) + F(3) = 1 + 2 = 3
F(5) = F(3) + F(4) = 2 + 3 = 5
와 같이 이어집니다.
2 이상의 n이 입력되었을 때, n번째 피보나치 수를 1234567으로 나눈 나머지를 리턴하는 함수, solution을 완성해 주세요.
제한사항
n은 2 이상 100,000 이하인 자연수입니다.
1차 풀이 - 단순 회귀 방식
낮은 숫자에 한해서는 가능하지만 n=50인 경우만 가도 눈에 띄게 느려짐.
n = 50 일 경우 25초 정도 소요됨.
public long getFibonacciNum(int n) {
if (n <= 0) {
return 0L;
} else if (n == 1 || n == 2) {
return 1L;
}
return getFibonacciNum(n - 2) + getFibonacciNum(n - 1);
}
2차 풀이 - 캐싱 방식
재귀 함수이므로 트리형식으로 뻗어나가기 때문에 같은 계산의 중복이 많아서(대략 2^n개까지 등장)라고 생각했다.
Fn = Fn-2 + Fn-1 이므로 Fn-2을 계산하는 동안 캐시 Array가 채워져 있으면 다음 계산이 빨라진다고 생각했고 다음과 같이 코드를 작성했다.
public long getFibonacciNumWithCache(int n) {
if (cache == null) {
cache = new long[n+1];
cache[0] = 0L;
cache[1] = 1L;
cache[2] = 1L;
}
if (n <= 0) {
return cache[0];
} else if (n == 1) {
return cache[1];
}
cache[n] = cache[n - 2] + cache[n - 1];
if (cache[n] != 0) {
return cache[n];
}
return getFibonacciNumWithCache(n - 2) + getFibonacciNumWithCache(n - 1);
}
위에서 했던 방식으로 n = 50 일 때도 빠르게 작동했다.
하지만 n이 3만 중반대를 넘어가면서 StackOverflow가 떴다.
3차 풀이 - 비네의 식을 이용한 풀이
구하는 방식이 너무 느린것인가 싶어 다음 포스팅을 찾아 그대로 풀어보았다.
[Python] 피보나치 수열 7.7배 빠르게 계산하는 방법
파이썬으로 피보나치 수열을 빠르게 구하는 방법 피보나치 수열이란 0과 1로 시작하여 이전 두 숫자의 합을 나열하는 것을 말합니다 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... 피보나치 수열에서 1,00
deepwelloper.tistory.com
public double getFibonacciNumByFormula(int n) {
double sqrt5 = Math.sqrt(5);
return (1d/sqrt5) * (Math.pow((1d+sqrt5)/2d, n) - Math.pow((1d-sqrt5)/2d, n));
}
하지만, 이것이 문제가 아니었다. 피보나치 수가 너무 커지는 나머지 담을 수가 없었던 것이 이유였다.
어떤 다른 방식의 아이디어가 있는지 프로그래머스 팁 게시판에서 찾아보았다.
4차 풀이 - 비네의 식을 이용한 풀이
나머지 값 또한 모듈러 연산의 성질에 따라 쪼갤 수 있다는 것이 키포인트 였고, 1234567로 나누는 것은 int안에 머물게 함을 의미하는 것이었다.
public long getFibonacciNumNoRecursion(int n) {
if (cache == null) {
cache = new long[n + 1];
cache[0] = 0L;
cache[1] = 1L;
}
for (int i = 0; i <= n; i++) {
if (i >= 2) {
long sum = cache[i - 2] + cache[i - 1];
cache[i] = sum % 1234567L;
}
}
return cache[n];
}
이번에는 회귀를 사용하지 않았고, 위의 설명에 따라 매번 계산 단계에서 나누기를 해주니 정답 처리가 되었다.
느낀 점
오래걸려 헤메었지만 속도 및 알고리즘의 종류만이 중요한 것이 아님을 깨달았다. 자료형과 예상되는 값의 크기를 간과하였고 문제를 풀음에 있어 1,2,3차까지 헤메게 만들었다. 37000번째의 피보나치 수가 long에 담길 것이라고 당연하게 생각하여 문제를 키운 것 같았다.( long이면 충분히 큰 타입이라고 생각했다.)
추가로 모듈러 연산에 대한 지식도 알게 되어 추 후 이런 문제가 나올 경우 도움이 많이 될 듯 싶다.
'Web Dev' 카테고리의 다른 글
OAuth2의 4가지 프로토콜 (0) | 2024.02.22 |
---|---|
[cote] 예상 대진표 (1) | 2023.04.08 |
[IDE] Intelli J 단축키 (0) | 2023.03.23 |
[IDE] Intelli J - 상속 및 UML 확인하기 (0) | 2023.03.22 |
[cote] 기사단원의 무기 (0) | 2023.03.22 |