프로젝트 오일러 31
영국 화폐 액면가를 조합하는 방법의 수
재귀를 이용하면 쉽게 풀 수 있다. 동전의 종류를 숫자 1, 2, ...로 표현하면 문제를 풀 때 도움이 된다. 따라서 다음과 같이 종류 숫자를 인자로 주면 동전 값을 리턴하는 함수를 만들 수 있다.
(defn- value [n]
(case n
1 1
2 2
3 5
4 10
5 20
6 50
7 100
8 200))
Clojure에서는 벡터를 함수로 사용할 수도 있으므로 다음과 같이 벡터를 사용해 구현할 수도 있다.
(defn- value [n]
([1 2 5 10 20 50 100 200] (dec n)))
벡터의 인덱스는 0부터 시작하고 동전 종류 숫자는 1부터 시작하므로, dec
함수로 인자 값을 1 감소시켰다. 동전 종류 숫자를 0부터 시작하게 할 수도 있다. 그렇게 한다면 이 함수를 호출하는 쪽에서 그에 맞게 조건을 설정하면 된다.
편의상 동전을 금액의 오름차순으로 정렬해 놓았지만, 사실 순서는 상관없다. 두 구현 모두 충분히 간단하지만, 벡터를 이용한 구현은 dec
함수를 호출하는 부분이 있어서인지 case
를 사용한 구현보다 조금 느리다.
금액을 amt
라고 할 때, n
가지 동전을 가지고 amt
를 만드는 방법은 다음 두 가짓수의 합과 같다.
- 맨 처음 나오는 동전을 제외한 나머지 동전으로
amt
를 만드는 가짓수 - 처음 나온 동전 금액을
d
라 할 때amt - d
금액을n
가지 동전으로 만드는 가짓수
첫 번째 경우를 계산하려면 동전을 한 가지 제외하고 amt
를 만드는 가짓수를 구해야 한다. 사용할 수 있는 동전은 한 가지 줄어들었지만 문제는 동일하다. 두 번째 경우는 사용할 수 있는 동전은 그대로지만 금액이 줄어든 상태에서 가짓수를 구해야 하는데, 금액만 줄었지 문제는 동일함을 알 수 있다. 즉, 첫 번째 경우나 두 번째 경우 모두 동전 가짓수나 금액만 줄어들 뿐 로직(또는 알고리즘)은 그대로 쓸 수 있다. 즉 재귀를 사용할 수 있다.
재귀를 사용할 때는 항상 규칙이 끝나는 경우를 정리해야 한다.
amt
가 0이면 해당 금액을 만드는 방법은 1가지 밖에 없다.amt
가 0보다 작은 금액을 만드는 방법은 없다. (0가지)n
이 0이면 금액을 만드는 방법은 없다. (0가지)
이 규칙을 이용해 금액을 만드는 방법의 가짓수를 구하는 함수를 다음과 같이 만들 수 있다.
(defn- cc [amt n]
(cond
(= amt 0) 1
(< amt 0) 0
(= n 0) 0
:else (+ (cc amt (dec n))
(cc (- amt (value n)) n))))
문제를 다 푼 것이나 마찬가지다. (cc 200 8)
을 실행하면 답을 구할 수 있다. 다른 풀이와 코드 형식을 맞추기 위해 다음과 같이 solve
함수를 만든다.
(defn solve []
(cc 200 8))
실행 결과는 다음과 같다.
p031=> (time (solve)) "Elapsed time: 171.989327 msecs" 736??
참고
- 프로젝트 오일러 31 풀이 소스 코드
- 컴퓨터 프로그램의 구조와 해석, p50 동전 종류와 개수만 다를뿐 동일한 문제와 풀이가 있다. 프로젝트 오일러 31 풀이는 책의 설명을 글쓴이의 취향에 맞게 재구성한 것이다.