조합수를 구하는 공식 중 가장 널리 알려진 방식은
이지만, 이 문제에서는 DFS 연습을 위해 아래와 같은 공식을 사용한다.
n | r | result |
---|---|---|
5 | 3 | 10 |
33 | 19 | 818809200 |
조건: 3<= n <= 33 , 0 <= r <= n
nCr
이 n-1Cr-1
+ n-1Cr
과 같으므로, n
개 중 r
개를 뽑는 DFS(n,r) 함수를 작성하고, DFS 함수 내부에서 다시 DFS(n-1,r-1) 과 DFS(n-1,r) 을 호출하면서 경우의 수들을 더해나가면 된다. const DFS(n,r) = () => {
return DFS(n-1, r-1) + DFS(n-1, r);
}
n===r
일 때 (5C5는 언제나 1이므로), 또는 r === 0
일 때 (nC0 은 언제나 1이므로) 이다. const DFS(n,r) => () => {
if(n === r || r === 0) return 1;
else return DFS(n-1, r-1) + DFS(n-1, r);
}
여기까지만 해도 답을 구할 수는 있지만, 만약 n === 33
, r===19
과 같이 결과값이 조금이라도 큰 수를 구할 때는 문제가 생긴다.
위 그림에서 n과 r에 각각 33, 19 를 대입해보면, 트리의 가지가 셀 수 없이 많이 생기리란 걸 짐작할 수 있다.
하지만 만약에 가지를 아래로 뻗어 나가면서 이전에 계산했던 값을 저장해둔다면 어떨까?
아래쪽 노드에서 사용할 n, r 이 이전에 이미 계산해봤던 n, r 이라면 굳이 아래로 노드를 더 뻗어나갈 필요가 없다!
이러한 기술을 메모이제이션 이라고 하는데, 이전에 연산했던 값을 따로 저장해서 불필요한 재연산을 막는 방법이다.
const memo = Array.from({ length: 35 }, () =>
Array.from({ length: 35 }, () => 0)
);
문제에서 n 의 범위를 33까지 정해줬으므로, 조금 여유있게 35의 길이를 갖는 2차원 빈 배열을 생성했다. 2차원 배열인 이유는 n과 r 두 개의 인덱스가 변수로 작용하기 때문이다.
이제 위에서 작성한 DFS 함수에 메모이제이션을 적용해 불필요한 재연산을 막아보자.
const DFS = (n, r) => {
if (memo[n][r]) return memo[n][r];
if (n === r || r === 0) return 1;
else {
return (memo[n][r] = DFS(n - 1, r - 1) + DFS(n - 1, r));
}
};
DFS 함수가 실행되었을 때,
memo[n][r]
에 특정 값이 있으면 이미 전에 연산했던 nCr 이라는 뜻이므로,DFS (n,r) === memo[n][r]
이 된다.
const solution = (n, r) => {
let answer = 0;
const memo = Array.from({ length: 35 }, () =>
Array.from({ length: 35 }, () => 0)
);
const DFS = (n, r) => {
if (memo[n][r]) return memo[n][r];
if (n === r || r === 0) return 1;
else {
return (memo[n][r] = DFS(n - 1, r - 1) + DFS(n - 1, r));
}
};
DFS(n, r);
answer = memo[n][r];
return answer;
};