문제 정의
2048 게임은 4×4 크기의 보드에서 혼자 즐기는 재미있는 게임이다. 이 링크를 누르면 게임을 해볼 수 있다.
이 게임에서 한 번의 이동은 보드 위에 있는 전체 블록을 상하좌우 네 방향 중 하나로 이동시키는 것이다. 이때, 같은 값을 갖는 두 블록이 충돌하면 두 블록은 하나로 합쳐지게 된다. 한 번의 이동에서 이미 합쳐진 블록은 또 다른 블록과 다시 합쳐질 수 없다. (실제 게임에서는 이동을 한 번 할 때마다 블록이 추가되지만, 이 문제에서 블록이 추가되는 경우는 없다)
이 문제에서 다루는 2048 게임은 보드의 크기가 N×N 이다. 보드의 크기와 보드판의 블록 상태가 주어졌을 때, 최대 5번 이동해서 만들 수 있는 가장 큰 블록의 값을 구하는 프로그램을 작성하시오.
입력
첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.
출력
최대 5번 이동시켜서 얻을 수 있는 가장 큰 블록을 출력한다.
예제 입력 1
3
2 2 2
4 4 4
8 8 8
예제 출력 1
16
이 문제는 dfs, bfs 둘 다 사용해서 풀 수 있는 문제로 필자는 dfs로 접근해서 문제를 풀었다.
dfs 코드 작성은 쉽다. 상, 하, 좌, 우로 dfs를 돌리고 5번을 다 돌렸을 때 배열에서 가장 큰 값의 요소를 리턴해주면 4방향의 리턴값들 중 다시 최대값을 리턴하는 것이다.
가장 어려웠던 부분은 4방향으로 움직이는 함수들을 만드는 것이었다.
위로 움직였을 때의 상태를 예시로 들자면, 열순으로 두번째 행부터 탐색해주어야 한다. 이렇게 해주는 이유는 포인터를 사용하기 위함이다. 포인터는 가장 맨 앞쪽부터 위치해서 포인터가 가리키는 숫자들과 탐색되어지는 숫자들을 비교해 값을 변경하는 용도를 쓰여질 것이다. 이렇게 하는 이유는 경우의 수들을 보면 알 수 있다.
그럼 이제 경우의 수를 따져야 한다. 경우의 수는 3가지로 나뉠 수 있다.
- 포인터가 가리키는 수가 0일 때
- 포인터가 가리키는 수가 현재 위치의 수와 같을 때
- 포인터가 가리키는 수가 현재 위치의 수와 다를 때
첫 번째, 포인터가 가리키는 수가 0이라면 그 자리에 0이 아닌 수를 배치시켜야 하므로 현재 탐색한 수가 0이 아니면 포인터가 가리키는 곳에 탐색한 수를 넣는다. 현재 위치는 0으로 비워준다.
두 번째, 포인터가 가리키는 수가 현재 위치와 같다면 포인터의 수를 2배로 만들고 현재 위치는 0으로 만든 후에 포인터를 하나 증가시킨다. 포인터를 증가시키는 이유는 같은 수가 여러 개 연달아 있을 때 중복으로 더해질 수 있기 때문이다.
세 번째, 포인터가 가리키는 수가 현재 위치의 수와 다르면 포인터만 하나 증가시킨다.
전체코드
import sys
from copy import deepcopy
input = sys.stdin.readline
# INPUT
n = int(input())
board = [list(map(int, input().split())) for _ in range(n)]
answer = 0
# UP
def up(board):
for j in range(n):
pointer = 0
for i in range(1, n):
if board[i][j]:
tmp = board[i][j]
board[i][j] = 0
# 포인터가 가리키는 수가 0일 때
if board[pointer][j] == 0:
board[pointer][j] = tmp
# 포인터가 가리키는 수와 현재 위치의 수가 같을 때
elif board[pointer][j] == tmp:
board[pointer][j] *= 2
pointer += 1
# 포인터가 가리키는 수와 현재 위치의 수가 다를 때
else:
pointer += 1
board[pointer][j] = tmp
return board
# DOWN
def down(board):
for j in range(n):
pointer = n - 1
for i in range(n - 2, -1, -1):
if board[i][j]:
tmp = board[i][j]
board[i][j] = 0
if board[pointer][j] == 0:
board[pointer][j] = tmp
elif board[pointer][j] == tmp:
board[pointer][j] *= 2
pointer -= 1
else:
pointer -= 1
board[pointer][j] = tmp
return board
# LEFT
def left(board):
for i in range(n):
pointer = 0
for j in range(1, n):
if board[i][j]:
tmp = board[i][j]
board[i][j] = 0
if board[i][pointer] == 0:
board[i][pointer] = tmp
elif board[i][pointer] == tmp:
board[i][pointer] *= 2
pointer += 1
else:
pointer += 1
board[i][pointer]= tmp
return board
# RIGHT
def right(board):
for i in range(n):
pointer = n - 1
for j in range(n - 2, -1, -1):
if board[i][j]:
tmp = board[i][j]
board[i][j] = 0
if board[i][pointer] == 0:
board[i][pointer] = tmp
elif board[i][pointer] == tmp:
board[i][pointer] *= 2
pointer -= 1
else:
pointer -= 1
board[i][pointer] = tmp
return board
# DFS
def dfs(board, cnt):
if cnt == 5:
# 2차원 배열 요소 중 가장 큰 값 반환
return max(map(max, board))
# 상, 하, 좌, 우로 움직여 리턴한 값들 중 가장 큰 값 반환
# board를 꼭 deepcopy하여 함수를 거친 board값이 다음 함수에 들어가지 못하도록 해주어야 한다.
return max(dfs(up(deepcopy(board)), cnt + 1), dfs(down(deepcopy(board)), cnt + 1), dfs(left(deepcopy(board)), cnt + 1), dfs(right(deepcopy(board)), cnt + 1))
print(dfs(board, 0))
'문제풀이 > 구현' 카테고리의 다른 글
[파이썬] [구현] 백준 2908 상수 (0) | 2022.01.02 |
---|---|
[파이썬] [구현] 백준 14503 로봇 청소기 (0) | 2022.01.01 |
[파이썬] [구현] 백준 4673 셀프 넘버 (0) | 2021.12.31 |
[파이썬] [구현] 문자열 재정렬 (0) | 2021.12.23 |
[파이썬] [구현] 왕실의 나이트 (0) | 2021.12.22 |