알고리즘 - 메모이제이션 코드 패턴과 외발뛰기 문제
📌 동적 계획법(다이나믹 프로그래밍)에서 사용되는 메모이제이션
- 재귀 호출을 하는 과정에서 중복된 함수 호출이 있을 수 있는데, 이미 계산된 것은 다시 사용하여 효율성을 높이자는 것이 메모이제이션 기법입니다.
- 메모이제이션 사용 패턴
메모이제이션을 사용해서 함수를 구현할 때 아래와 같은 패턴을 알고 있다면 편할 것 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 전부 -1로 초기화
int cache[2500][2500];
// a, b 는 구간 안의 정수를 말하고, 반환 값은 항상 int형인 음이 아닌 정수
int Function(int a, int b){
//[1] 맨 처음에는 기저 사례 처리하기
if(...) return ...;
//[2] (a, b)에 대한 답을 구한 적이 있다면 그것을 캐시에서 바로 반환
int& ret = cache[a][b];
if(ret != -1) return ret;
//[3] 없었을 경우 답을 계산하여 넣기
...
return ret;
}
ret 변수가
cache[a][b]
에 대한 참조형 변수라서, 계산한 값을 캐시에 저장할 때 밑에서 매번cache[a][b]
라고 쓸 필요가 없습니다.memset으로 -1로 모두 초기화 하는 과정을 간편히 진행할 수 있습니다. 단, C++에서는 cstring을 헤더 파일로 선언해야 사용할 수 있습니다.
1
memset(cache, -1, sizeof(cache));
알고스팟 : 외발뛰기 문제
(https://algospot.com/judge/problem/read/JUMPGAME)
- 이 문제는 n*n 격자판에 적힌 숫자만큼 오른쪽 혹은 아래쪽으로 이동하면서, 맨 오른쪽 맨 아래 끝지점에 도달할 수 있는지 여부를 파악해야 합니다. 시작점은 맨 왼쪽 맨 위의 처음칸입니다.
특정 칸에서 도착 지점에 갈 수 있는지 여부를 반환하는 함수 하나를 만듭니다.
- 경로를 파악하는 와중에, 이전에 계산한 값을 또 이용해야 하는 경우가 반드시 생깁니다. 이것을 메모이제이션 기법으로 해결할 수 있습니다.
- 예) 오른쪽 혹은 아래로 칸을 이동하다가 (2, 2)에 도달했다고 가정합니다. 그런데 이전에도 (2, 2)에서 끝까지 갈 수 있는지 여부를 이미 파악해서 캐시 배열에 저장해둔 상태라면, 더 이상 칸을 이동하는 것을 하지 않고 그 값을 바로 반환받고 끝내면 됩니다.
- 이 문제도 위에서 언급한 메모이제이션 패턴을 그대로 사용하여 풀 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
typedef long long ll;
using namespace std;
int board[101][101];
int cache[101][101];
int n;
// 마지막 칸에 도달할 수 있는지 여부를 반환하는 함수
int move(int y, int x) {
// 1. 무조건 기저사례 먼저 처리
// 범위를 넘어갔을 경우
if (x >= n || y >= n) return 0;
// 도착했을 경우
if (x == n - 1 && y == n - 1) return 1;
// 2. 이미 캐시배열에 계산된 값이 있었을 경우 그것을 반환
int& ret = cache[y][x];
if (ret != -1) return ret;
// 3. 아닐 경우 계산하여 값 저장
// 칸의 이동 거리를 저장한다.
int movingSize = board[y][x];
// 아래로 혹은 오른쪽으로 이동시켜 본다.
// 도달할 수 없었을 경우는 cache[y][x]에 0이, 도착했을 경우 1이 저장된다.
return ret = (move(y + movingSize, x) || move(y, x + movingSize));
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
for (int i = 0; i < t; i++) {
cin >> n;
// 캐시배열 초기화
memset(cache, -1, sizeof(cache));
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
cin >> board[j][k];
}
}
int result = move(0, 0);
if (result == 1) cout << "YES\n";
else cout << "NO\n";
memset(cache, -1, sizeof(cache));
}
return 0;
}
이 포스팅은 알고리즘 문제해결 전략(구종만 저) 책의 일부를 정리한 것입니다.