난이도: 골드 2
문제 링크: www.acmicpc.net/problem/12100
출처: 삼성 SW역량 테스트
문제 설명이 긴 편이므로, 위 링크에서 직접 읽어 보시는 것을 추천드립니다.
잘 알려진 2048이라는 게임을 부분적으로 구현한 후,
최대 5번 움직을 때 얻을 수 있는 가장 큰 값을 출력하는 문제입니다.
'움직임' 이라고 함은 상하좌우 네 방향으로 숫자들을 이동시키는 것을 말하며, 이 때 이동하는 방향 중 값이 같고 아직 합쳐지지 않은 숫자를 만났다면 두 수를 더합니다.
예를 들어 2 2 4 0 8 이라는 숫자가 있다면, 왼쪽으로 한번 이동하면 4 4 8 0 0, 한번 더 이동하면 8 8 0 0 0, 한번 더 이동하면 16 0 0 0 0의 결과를 얻게 됩니다.
저는 이렇게 접근했습니다.
기존 배열을 board[i][j]라고 하겠습니다.
다음과 같은 배열이 입력으로 들어온다고 가정합시다 (문제에서 주어진 테스트케이스를 약간 변형하였습니다).
2 | 2 | 4 |
4 | 4 | 4 |
8 | 8 | 8 |
board[i] = 2, 2, 2가 됩니다.
이제 숫자를 왼쪽으로 모으는 경우, 문제에서 요구하는 조건대로라면 board[0][0]과 board[0][1]의 값이 더해져 board[0][0]에 저장되고, board[0][2]의 값이 board[0][1]로 이동해야 합니다. 이 때 새롭게 board[0][0]에 저장되어 있는 4는 이미 덧셈이 한번 이루어진 결과로써 저장된 값이므로, board[0][2]에 있던 값과 더해지면 안 됩니다.
즉, 1회 이동시킴을 왼쪽으로 하였을 때 얻을 결과는 다음과 같습니다.
4 | 4 | 0 |
8 | 4 | 0 |
16 | 8 | 0 |
0은 숫자가 실제로 존재하는 것이 아닌, 빈 칸을 의미합니다.
i = 0일 때를 기준으로 설명하겠습니다.
저는 위 로직을 구현하기 위해 스택과 큐를 사용했습니다.
우선, board[0][j]의 모든 값을 stack s에 역순으로 담습니다. board[0][2], board[0][1], board[0][0]을 순서대로 담으면, 스택은 LIFO 구조이므로, stack s 내의 값은 {2, 2, 4}가 됩니다.
그 다음, board[0][j]의 값을 모두 0으로 설정합니다. 이것은 해당 행의 값을 우선 전부 비웠음을 뜻합니다.
그 후, 다음 과정을 반복합니다.
- 스택에서 값을 하나 pop 해서 변수 first에 담습니다.
- 스택의 상태를 확인합니다. 스택이 비었을 경우, first의 값을 queue q에 삽입하고 종료합니다. 스택이 비어있지 않은 경우, 스택에서 값을 하나 pop해서 변수 second에 담습니다.
- first와 second의 값을 비교해 봅니다. 두 값이 같은 경우, 두 값을 더해서 queue q에 삽입합니다. 같지 않은 경우, first를 q에 삽입하고, second를 s에 삽입합니다.
- 위 과정을 스택 s가 완전히 비게 될 때까지 반복합니다.
현재 스택 s = {2, 2, 4}, q = {}, board[0] = {0, 0, 0} 입니다.
위 과정을 따라서 진행해 보겠습니다.
1. first = 2가 됩니다. 이후 stack.size() == 2이므로, second = 2가 됩니다. first == second 이므로, 둘을 더한 값 4를 q에 삽입합니다.
따라서 s = {4}, q = {4}, board[0] = {0, 0, 0} 입니다.
2. first = 4가 됩니다. 이후 stack.size() == 0이므로, first의 값을 q에 삽입하고 종료합니다.
따라서 s = {}, q = {4, 4}, board[0] = {0, 0, 0} 입니다.
이제 계산한 값을 board에 반영해 주면 됩니다.
q의 index를 하나하나 board에 옮겨 주면 됩니다. pseudocode로 나타내면 대략 이렇게 됩니다.
j = 0
while q is not empty:
now = q.top
q. pop
board[0][j] = now
j++
q의 크기가 2이므로, 위 loop는 딱 두 번 반복되게 됩니다.
board[0][0] = 4, board[0][1] = 4 가 되어, 최종적으로 q를 비우고 나면 board[0] = {4, 4, 0}이 됩니다.
성공적으로 값을 왼쪽으로 이동시킨 것을 확인할 수 있습니다.
위와 같은 접근 방법은 j의 segmentation error도 걱정하지 않아도 됩니다.
덧셈이 한 번도 이루어지지 않아 queue의 크기가 최대가 되는 최악의 경우를 고려해 보겠습니다.
행렬 board의 크기가 5*5이고, board[0] = {2, 4, 8, 16, 32}라고 가정합시다.
이때 stack s = {2, 4, 8, 16, 32}가 되고, stack을 비우면서 덧셈이 한 번도 이루어지지 않기 때문에 queue q = {2, 4, 8, 16, 32}가 됩니다.
즉 q의 크기 <= board의 크기 가 되며, 최악의 경우에 등호가 성립합니다.
따라서 board의 범위를 벗어나는 index를 접근하는 경우는 없습니다.
위 문제의 경우 시뮬레이션 및 구현의 성질도 포함하고 있습니다.
정확히는 각 값이 이동하는 점은 시뮬레이션, 이를 통해 가능한 경우의 수를 완전 탐색하는 것은 구현에 속한다고 할 수 있습니다.
또, 각 방향에 따라, 총 5회 이동할 때까지 탐색을 해야 하기 때문에, depth가 5일 때까지 탐색하는 DFS에 포함된다고도 할 수 있습니다.
다만 각 방향 (상하좌우) 모두 코드를 따로 써야 하는데, 기본적인 로직은 같지만 세세한 인덱스만 다르고, 또 필연적으로 코드가 매우 길어지기 때문에 오타 등이 생겨도 찾아내기 정말 어렵습니다.
따라서 코딩테스트에서 이런 문제를 마주한다면, 코드를 작성할 때 인덱스값 부분에서 실수를 하지 않도록 아주 주의해야 합니다.
'알고리즘, 문제해결 > 알고리즘 문제풀이' 카테고리의 다른 글
[백준 5719번] 거의 최단 경로 (0) | 2021.04.29 |
---|---|
[백준 13460번] 구슬 탈출 2 (0) | 2021.03.24 |
[백준 15401번] 퇴사 (0) | 2021.03.11 |
[프로그래머스] 가사 검색 (0) | 2021.03.08 |
[백준 2110번] 공유기 설치 (2) | 2021.03.08 |
댓글