grid
: 0, 1, 2를 담고 있는 2차원 행렬 ()
n x m 크기의 2차원 행렬 grid
는 0 또는 1 또는 2라는 정수가 담겨 있다.
특정 규칙과 조건에 따라 만들 수 있는 V-shaped diagonal segment의 최대 길이를 구하는 문제이다.
V-shaped diagonal segment
이란?
2, 0, 2, 0, ..
이란 패턴을 따라 이동이 문제는 위치, 방향, 턴 여부, 다음 기대값
이란 다양한 복합 상태를 보존하면서 최대 길이를 구해야 한다.
그냥 DFS/BFS만 사용한다면 시간 초과 에러가 발생한다.
따라서, 중복 상태를 재사용할 수 있도록 DP(메모이제이션) 방식을 함께 적용해야 한다.
→ DFS로 모든 경로 탐색 & 중복 상태는 메모이제이션으로 재사용해 효율 상승
1️⃣ DFS
2️⃣ 메모이제이션
@lru_cache(None)
DFS + 메모이제이션 →
DFS 내 이동 →
최종 시간복잡도
최악의 경우 이므로 충분히 동작할 수 있다.
시간 초과 에러
가 발생했다.class Solution:
def lenOfVDiagonal(self, grid):
n, m = len(grid), len(grid[0])
dx = [1, 1, -1, -1]
dy = [1, -1, -1, 1]
from functools import lru_cache
# DFS + 메모이제이션 함수
# x, y 위치, 방향 d, 턴 사용 여부 turn(0/1), next 기대값(2 또는 0)
@lru_cache(None)
def dfs(x, y, d, turn, next_val):
# 범위 벗어나거나 패턴 불일치 시 0 반환
if not (0 <= x < n and 0 <= y < m) or grid[x][y] != next_val:
return 0
# 다음 기대값 토글(2 <-> 0)
next_expect = 0 if next_val == 2 else 2
res = 0
# 직진
res = max(res, 1 + dfs(x + dx[d], y + dy[d], d, turn, next_expect))
# 턴 가능하면 한 번 턴
if turn == 0:
nd = (d + 1) % 4 # 시계 방향으로 한 번 턴
res = max(res, 1 + dfs(x + dx[nd], y + dy[nd], nd, 1, next_expect))
return res
max_len = 0
# 시작점은 grid[i][j] == 1이고, next_val은 2부터 시작
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
for d in range(4):
max_len = max(max_len, 1 + dfs(i + dx[d], j + dy[d], d, 0, 2))
return max_len
class Solution:
def lenOfVDiagonal(self, grid: List[List[int]]) -> int:
# Dynamic Programming
# Let dp(x, y, t, d) be the longest segment starting with (x, y) where the segment has been turned or not (indicated by the binary flag t) and the current direction is d.
# When grid(x, y) == 1:
# dp(x, y, t, *) = max(dp(x', y', true, d) if grid(x', y') == 2, 1)
# Otherwise:
# dp(x, y, t, d) = max(dp(x', y', t, d) if grid(x',y') == 2 - grid(x, y) else 1, dp(x'', y'', false, d') if d is true and grid(x'',y'') == 2 - grid(x, y))
# The overall complexity is O(m * n * 4 * 2) ~ O(2 * 10^6).
# 방향 벡터는 네 가지 대각선 방향을 의미
dirs = [(1, 1), (1, -1), (-1, -1), (-1, 1)]
n = len(grid)
m = len(grid[0])
# 다음 기대값을 담은 배열. 인덱스는 현재칸의 값(grid[x][y])이고, 값은 해당 칸 이후에 와야 할 숫자를 뜻한다
nv = [2, 2, 0] # Next expected value
# helper 함수는 재귀로 최대 길이를 찾는데, 캐시를 사용해 중복 상태를 저장해 계산 속도를 높인다.
# x, y: 현재 위치 좌표
# turned: 90도 방향 전환을 했는지 여부 (True/False)
# dir: 현재 이동 방향 인덱스 (dirs 배열에서 0~3)
@functools.cache
def helper(x, y, turned, dir):
# First, we do not change the direction
# 최소 길이
res = 1
dx, dy = dirs[dir]
if 0 <= x + dx < n and 0 <= y + dy < m and grid[x + dx][y + dy] == nv[grid[x][y]]:
res = helper(x + dirs[dir][0], y + dirs[dir][1], turned, dir) + 1
if not turned:
dx, dy = dirs[(dir + 1) % 4]
if 0 <= x + dx < n and 0 <= y + dy < m and grid[x + dx][y + dy] == nv[grid[x][y]]:
res = max(res, helper(x + dx, y + dy, True, (dir + 1) % 4) + 1)
return res
ans = 0
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
# Optimization: we can compute the theorically longest path. If the current answer is already better than this, we do not need to make the DFS.
# 최적화: 이론상 최장 경로 길이 추정 후 탐색 생략 가능
tm = (n - i, j + 1, i + 1, m - j)
for d in range(4):
# 이론상 최대 길이가 지금까지 구한 답보다 크면 탐색
if tm[d] > ans:
ans = max(ans, helper(i, j, False, d))
return ans