이 문제를 읽고 나서 바로 그래프 문제라는 것을 깨달았다. 판단 근거는 다음과 같다.
그래프 문제라는 것을 깨달았고, 한 노드에서 시작하여 갈 수 있는 최대한 많은 칸을 가야하기 때문에 DFS 탐색 자료구조를 사용하기로 결심했다. DFS를 사용한 로직은 다음과 같다.
이러한 로직으로 제출한 결과, 시간초과 & 메모리 초과
판정을 받았다. 처음에는 이해가 가지 않았다. 완전 탐색을 하긴 했지만, n의 범위가 1 ≤ n ≤ 500
이었고, 경로에 대한 시간 복잡도는 대략 500 x 500 = 250,000 정도이다. 방향성이 큰 값을 향해서만 가도록 정해져있기 때문에 각 노드마다 4방향 탐색을 하더라도 충분할 것이라고 생각했는데, 내가 판단을 잘못했다.
위 그림에서 각 숫자들이 노드 번호라고 할 때, 1번 노드만 생각하자. 상, 하, 좌, 우 범위를 넘지않고, 큰 값만을 찾아가기 위해서 정말 여러 방향으로 움직이며 16을 향해 갈 것이다. 탐색을 끝내고, 2번 노드로 움직여서 다시 DFS 탐색을 하려고 한다면, 1번 노드에서 시작했던 것과 거의 비슷하게 움직일 것이다. 이 MAP이 500 x 500 이라고 생각한다면 정말 많은 탐색을 할 것으로 예상된다.
위 그림 예시를 처음 작성한 코드로 실행하고, 함수 내 동작 횟수를 다음과 같은 코드로 카운팅 해봤다.
def dfs(cur_y, cur_x, cnt):
global MAX, total_move
total_move += 1 # 동작 횟수 증가
MAX = max(MAX, cnt)
for dy, dx in zip(dys, dxs):
nxt_y, nxt_x = cur_y + dy, cur_x + dx
if 0 <= nxt_y < n and 0 <= nxt_x < n and MAP[cur_y][cur_x] < MAP[nxt_y][nxt_x]:
dfs(nxt_y, nxt_x, cnt + 1)
그 결과 총 546번
의 탐색을 실행한 것을 확인할 수 있었다. 죽, 4 x 4 = 16 이 아니라 4 x 4 MAP임에도 불구하고 500번이 넘는 탐색을 한 것이다. 500 x 500 MAP 에서 시간초과가 나는 현상은 지극히 당연한 결과였다.
시간초과가 났던 원인은 방문 처리를 하지 않았기 때문이다. 즉, 선언했던 dp 2중 리스트를 전혀 사용하고 있지 않아서 생긴 문제였다. 다이나믹 프로그래밍 기법을 같이 적용하려고 하니, 감이 아예 잡히지 않아 이를 잘 풀이한 블로그를 찾아서 공부했다. 메모이제이션을 활용하여 DFS 탐색을 하면서도 현재 위치에서 최대로 뻗어나갈 수 있는 최대 노드 개수를 지속적으로 갱신해보자. 그러면 방문했던 곳을 재방문 시 해당 노드에 저장된 값을 통해 더 이상의 탐색을 하지 않아도 되므로 시간 절약을 할 수 있다.
DFS 탐색은 아무래도 재귀 자료구조이기 때문에 추론하기가 좀 힘든 것 같다. 따라서 정확한 동작 방식을 이해하는 것이 중요하다. 또한, DFS 탐색과 DP 알고리즘이 결합된 문제들이 기업 코테에서도 종종 등장하기 때문에 이와 같은 유형의 문제를 충분히 풀어보는 것도 중요한 것 같다.
DP 알고리즘을 적용하니, 정답 판정을 받았다!
from sys import stdin, setrecursionlimit
setrecursionlimit(25 * (10 ** 4))
input = stdin.readline
def dfs(cur_y, cur_x, cnt):
global MAX
MAX = max(MAX, cnt)
for dy, dx in zip(dys, dxs):
nxt_y, nxt_x = cur_y + dy, cur_x + dx
if 0 <= nxt_y < n and 0 <= nxt_x < n and MAP[cur_y][cur_x] < MAP[nxt_y][nxt_x]:
dfs(nxt_y, nxt_x, cnt + 1)
n = int(input().rstrip())
MAP = [list(map(int, input().split())) for _ in range(n)]
MAX = 0
dys, dxs = [-1, 1, 0, 0], [0, 0, -1, 1]
for i in range(n):
for j in range(n):
dp = [[-1 for _ in range(n)] for _ in range(n)]
dfs(i, j, 1)
print(MAX)
from sys import stdin, setrecursionlimit
setrecursionlimit(10 ** 8)
input = stdin.readline
def dfs(cur_y, cur_x):
global MAX
if dp[cur_y][cur_x] != -1:
return dp[cur_y][cur_x]
dp[cur_y][cur_x] = 1 # 기본 방문 체크
for dy, dx in zip(dys, dxs):
nxt_y, nxt_x = cur_y + dy, cur_x + dx
# 범위를 넘지 않고, 현재 위치의 대나무보다 다음 위치의 대나무가 더 많다면 -> 탐색
if 0 <= nxt_y < n and 0 <= nxt_x < n and MAP[cur_y][cur_x] < MAP[nxt_y][nxt_x]:
cnt = 1
cnt += dfs(nxt_y, nxt_x)
dp[cur_y][cur_x] = max(dp[cur_y][cur_x], cnt)
MAX = max(MAX, dp[i][j])
return dp[cur_y][cur_x]
n = int(input().rstrip())
MAP = [list(map(int, input().split())) for _ in range(n)]
dp = [[-1 for _ in range(n)] for _ in range(n)]
MAX = 1
dys, dxs = [-1, 1, 0, 0], [0, 0, -1, 1]
for i in range(n):
for j in range(n):
if dp[i][j] == -1:
dfs(i, j)
print(MAX)