소수란 1보다 큰 자연수 중에서 1과 자기 자신을 제외한 다른 자연수로 나누어떨어지지 않는 자연수이다. 코딩 테스트에서는 특정한 자연수가 소수인지 아닌지를 판별하는 문제가 자주 출제된다.
def is_prime_number(x):
for i in range(2, x):
if x % i == 0:
return False
return True
위 코드의 시간 복잡도는 2부터 X-1까지 모든 자연수를 하나씩 확인하므로 O(X)이다. 하지만 약수의 대칭성을 이용하면 시간 복잡도를 O(√X)로 줄일 수 있다.
어떤 수 N의 약수는 일반적으로 (a, b) 형태의 쌍으로 존재한다. 여기서 a * b = N을 만족하므로, 두 수 중 하나는 반드시 √N 이하이다. 즉, b가 √N보다 크다면, a는 √N 이하일 수밖에 없다.
예를 들어, 30의 약수를 생각해보자.
여기서 5보다 큰 수들은 이미 앞에서 확인한 약수 쌍에 포함되었음을 알 수 있다. 즉, 30의 제곱근인 약 5.47까지만 확인하면 모든 약수 관계를 파악할 수 있다. 따라서 X의 소수 여부를 판별할 때, 2부터 √X까지만 확인하면 충분하다.
import math
def is_prime_number(x):
for i in range(2, int(math.sqrt(x)) + 1):
if x % i == 0:
return False
return True
에라토스테네스의 체는 특정한 범위 내에서 모든 소수를 찾을 때 사용할 수 있는 알고리즘이다.
import math
n = 1000
array = [True for _ in range(n + 1)]
for i in range(2, int(math.sqrt(n)) + 1):
if array[i]:
j = 2
while i * j <= n:
array[i * j] = False
j += 1
for i in range(2, n + 1):
if array[i]:
print(i, end=' ')
약수의 대칭성은 에라토스테네스의 체에서도 적용할 수 있다. 따라서, sqrt(n)까지만 순회해도 충분하다.
에라토스테네스의 체 알고리즘의 시간 복잡도는 사실상 선형 시간에 가까울 정도로 매우 빠르다 → O(NloglogN)
다수의 소수를 찾아야 하는 문제에서 효과적으로 사용될 수 있으나, 각 자연수에 대한 소수 여부를 저장해야 하므로 메모리가 많이 필요하다.
투 포인터 알고리즘은 리스트에 순차적으로 접근해야 할 때, 두 개의 점을 사용하여 처리하는 알고리즘이다.
예를 들어, 2번부터 7번까지의 학생을 지목해야 할 때, 각각 한 명씩 부르는 대신 "2번부터 7번까지"라고 표현하는 것처럼, 리스트 내 특정 범위의 데이터를 효율적으로 탐색할 수 있다.
일반적인 방법으로는 O(N²)의 시간이 소요될 수 있지만, 투 포인터 알고리즘을 이용하면 O(N)으로 해결할 수 있다.
n = 5
m = 5
data = [1, 2, 3, 2, 5]
count = 0
interval_sum = 0
end = 0
for start in range(n):
while interval_sum < m and end < n:
interval_sum += data[end]
end += 1
if interval_sum == m:
count += 1
interval_sum -= data[start]
print(count)
이 밖에도 투 포인터 알고리즘은 정렬된 두 리스트의 합집합을 구하는 문제에도 사용할 수 있다.
n, m = 3, 4
a = [1, 3, 5]
b = [2, 4, 6, 8]
result = [0] * (n + m)
i = 0
j = 0
k = 0
while i < n or j < m:
if j >= m or (i < n and a[i] <= b[j]):
result[k] = a[i]
i += 1
else:
result[k] = b[j]
j += 1
k += 1
for i in result:
print(i, end=' ')
이 알고리즘의 시간 복잡도는 O(N + M)이다. 이는 단순히 각 리스트의 모든 원소를 한 번씩만 순회하면 되기 때문이다. 해당 알고리즘은 병합 정렬과 같은 일부 알고리즘에서도 사용된다.
구간 합 문제는 연속적으로 나열된 N개의 수가 있을 때 특정 구간의 모든 수를 합한 값을 계산하는 문제다. 이러한 문제는 여러 개의 쿼리로 구성되는 형태가 많다.
이를 해결하기 위해 접두사 합(Prefix Sum)을 사용한다. 접두사 합이란 배열의 맨 앞부터 특정 위치까지의 합을 미리 구해 놓은 것이다.
P[Right] - P[Left - 1]로 구한다.n = 5
data = [10, 20, 30, 40, 50]
# Prefix Sum
sum_value = 0
prefix_sum = [0]
for i in data:
sum_value += i
prefix_sum.append(sum_value)
left = 3
right = 4
print(prefix_sum[right] - prefix_sum[left - 1])