1487, 4817, 8147은 3330씩 늘어나는 등차수열입니다. 이 수열에는 특이한 점이 두 가지 있습니다. 세 수는 모두 소수입니다. 세 수는 각각 다른 수의 자릿수를 바꿔서 만들 수 있는 순열(permutation)입니다. 1자리, 2자리, 3자리의 소수 중에서는 위와 같은 성질을 갖는 수열이 존재하지 않습니다. 하지만 4자리라면 위엣것 말고도 또 다른 수열이 존재합니다. 그 수열의 세 항을 이었을 때 만들어지는 12자리 숫자는 무엇입니까?
http://euler.synap.co.kr/prob_detail.php?id=49
접근
문제에서 제시한 범위가 4자리수 (1234 ~ 9876) 내에 있기 때문에 범위는 제법 좁은 편이지만, 효과적으로 조건에 맞는 값을 찾도록 하는 것이 제법 성가시다. 일단 답을 찾는 과정을 다음과 같이 확인할 수 있다.
- 모든 후보가 되는 수는 소수이다. 따라서 1,000 이상 10,000 이하의 소수를 소수체를 사용하여 모두 구한다.
- ‘5617’, ‘7561’ 등 같은 숫자의 순열인 것들을 각각의 리스트로 묶는다. 이를 위해서는 각 수의 숫자들을 크기 순으로 정렬한 최소순열을 기준으로 구분하여, 하나의 사전에 (최소순열, 순열리스트)의 형식으로 저장한다.
- 사전에 저장된 값 중에서 길이가 3이상인 것들만 대상으로, 소수들의 등차수열이 존재하는지 찾는다.
먼저 아래와 같이 소수체를 만드는 함수와 최소순열을 구하는 함수를 작성한다.
from functools import reduce
def sieve(n: int) -> list[int]:
s = [False] * 2 + [True] * (n - 1)
for i in range(int(n ** 0.5 + 1.5)):
if s[i]:
s[i+i::i] = [False] * ((n - i) // i)
return [i for i, x in enumerate(s) if x]
def make_key(n: int) -> str:
return ''.join(sorted(list(str(n))))
등차수열 찾기
만약 4개나 5개가 같은 순열일 때, 이 중 3개가 등차수열을 이루는지 확인하는 방법은 무엇일까? 정렬된 리스트에서 2개를 고르고 그 차이와 고른 두 개의 수 중 큰 수를 더한 값이 다시 그 리스트에 있는지를 검사하는 방법을 사용할 수 있다. 아니면 성능은 조금 떨어질지 몰라도, n 개 중에서 3개의 조합을 구하고 그 세 수가 등차수열인지를 찾으면 된다.
전자의 방법
def test(xs: list[int]) -> bool:
for i, x in enumerate(xs[:-2]):
for j, y in enumerate(xs[i+1:-1]):
if (2 * y - x) in xs:
return [x, y, y * 2 - x]
return None
좀 더 복잡하고 세련된 방법으로는, 3 개의 수를 고르는 조합에서 등차수열을 찾는 것이다.
def combinations(xs, n=3):
def helper(head, tail, k):
if k == 0:
return [head]
res = []
for (i, x) in enumerate(tail):
res.extend(helper([*head, x], tail[i+1:], k-1))
return res
return helper([], xs, n)
def test(xs):
for a, b, c in combinations(xs, 3):
if b * 2 == c - a:
return [a, b, c]
return None
문제를 푸는데는 어떤 방식을 써도 상관 없다.
seqs = {}
# 소수들을 같은 순열끼리 묶기
for p in filter(lambda x: x > 99, sieve(10000)):
seqs.setdefault(shrink(p), list()).append(p)
# 각각 순열리스트에서 등차수열이 있으면 출력하기
for ds in seqs.values():
if len(ds) < 3:
continue
res = test(ds)
if res:
print(''.join(map(str, res)))
이 코드에서는 전수 검사를 하고, 두 개의 답을 찾은 후에도 중간에 멈추지 않는다. 워낙에 금방 끝나는 작업이라 따로 루프를 탈출하지 않았다.
좀 더 생각해보기
문제에서 제시한 순열의 3개 소수 외에 이 문제의 정답도 공차가 3330으로 동일한 순열인데, 이게 특별한 의미가 있는 것인지 참 공교롭게 느껴진다. 위 코드를 조금 수정하면 순열이면서 등차수열을 이루는 4개의 소수 조합을 1개(83987, 88937, 93887, 98837)찾을 수 있는데, 이 때의 공차는 4950으로, 3330은 우연한 경우인 것 같다.
참고로 순열인 수들끼리의 차는 무조건 9의 배수가 된다.