Posts 알고리즘 - 메모이제이션 코드 패턴과 외발뛰기 문제
Post
Cancel

알고리즘 - 메모이제이션 코드 패턴과 외발뛰기 문제


알고리즘 - 메모이제이션 코드 패턴과 외발뛰기 문제

📌 동적 계획법(다이나믹 프로그래밍)에서 사용되는 메모이제이션

  • 재귀 호출을 하는 과정에서 중복된 함수 호출이 있을 수 있는데, 이미 계산된 것은 다시 사용하여 효율성을 높이자는 것이 메모이제이션 기법입니다.

  • 메모이제이션 사용 패턴

메모이제이션을 사용해서 함수를 구현할 때 아래와 같은 패턴을 알고 있다면 편할 것 같습니다.

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;
}

이 포스팅은 알고리즘 문제해결 전략(구종만 저) 책의 일부를 정리한 것입니다.