알고리즘에서 시간복잡도를 배운 기념으로 풀어보았다.
문제 풀이
시간복잡도라고도 불리는 러닝타임을 줄이는 방향으로 소수(Prime Number)를 판별해보자
문제 해석
Task
- 소수는 자연수 중 1보다 큰 수로, 1과 자기 자신 외에는 나눠지지 않는 수이다. 소수일 경우 ‘Prime’을, 아닐 경우 ‘Not Prime’을 출력하는 코드를 짜보자.
- 여기서 시간복잡도를 O(n)이 아니라 O(n제곱근)으로 하도록 노력해보라.
Input Format
- 첫 번째 줄은 test case의 개수, T가 입력된다.
- 두 번째 줄부터는 소수인지 판별해야 하는 정수 n이 입력된다.
문제 풀이
시행착오
- 처음에 단순하게 들어오는 수를 num이라는 변수에 받아 2부터 num까지의 range에서 모든 수로 나눈 나머지에 따라 소수인지 판별하게 했더니 1을 입력했을 때의 경우가 소수로 판별되어버렸다.
- 가장 처음에
if num == 1
을 넣고 나머지 식을 else문에 넣어주었다. - 이제는 러닝타임 오래 걸려 불합격. 하긴, 문제 자체가 시간복잡도에 대한 건데 이렇게 무식하게 짜면 안되지… 그러나 내 머리로는 해결이 안돼서 검색찬스. 생각보다 간단한 문제였다.
- 1과 자기자신 사이에서 나눗셈이 가능한 경우는 자기자신/2 까지의 수밖에 없으므로 num/2까지로 range를 제한하면 일단 for문 도는 횟수가 절반으로 준다. 하지만 여전히 시간복잡도는 O(n)
- 더 효율적인 계산을 위해서는, 약수의 중간값까지만 계산하는 것이 바로 O(n제곱근)으로 처리가 가능한 방법이다. 약수는 항상 쌍을 이루므로 그 가운데 값까지만 계산하면 소수인지 아닌지 판별이 가능하다.
- 여기서 약수의 중간값은 자기자신의 제곱근값에 근사하므로, math를 import하여
math.sqrt(num)
값으로 range의 end값을 넣어준다. - range는 정수만을 인수로 받으므로
math.floor
로 해당 수보다 작은 수 중 가장 큰 정수를 데려온 후 +1을 해주면 우리가 원하는 범위의 값 안에서만 for문이 돌도록 할 수 있다.
- 여기서 약수의 중간값은 자기자신의 제곱근값에 근사하므로, math를 import하여
코드 작성하기
1 | import math |
느낀 점
- 이렇게 하면 예컨대 80의 소수판별을 위해 80번 포문이 도는 게 아니라 8번 포문이 돈다. 10%로 시간이 줄어드는 혁신적인 방법이다.
- 머리가 나쁘면 손발이 고생하듯 개발자 머리가 나쁘면 컴퓨터가 고생을 한다. 최대한 컴퓨터가 덜 일하는 쪽으로 잘 해보자.
- range 안의 math 메서드들도 조금 지저분한데 정리할 수 있는 방법은 없을까…