문제
- 문제 링크
- 문제 설명: 현재 카드가 놓인 상태를 나타내는 2차원 배열 board와 커서의 처음 위치 r, c가 매개변수로 주어질 때, 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.
- 유저가 카드를 2장 선택하여 앞면으로 뒤집었을 때 같은 그림이 그려진 카드면 해당 카드는 게임 화면에서 사라지며, 같은 그림이 아니라면 원래 상태로 뒷면이 보이도록 뒤집음. 이와 같은 방법으로 모든 카드를 화면에서 사라지게 하면 게임이 종료
- 커서 키 조작은 상하좌우로 1칸씩 이동가능하며, 상하좌우에서 가장 가까운 카드로 이동하거나 없으면 마지막 칸으로 이동하는 점프 조작도 있다.
- 1칸 이동, 점프 이동, 카드 뒤집기 모두 1회 조작한 것으로 본다.
- 입력
- board는 4 x 4 크기의 2차원 배열
- board 배열의 각 원소는 0 이상 6 이하인 자연수 (0은 빈칸)
- 1 부터 6까지의 자연수는 2개씩 들어있으며 같은 숫자는 같은 그림의 카드를 의미
- 뒤집을 카드가 없는 경우(board의 모든 원소가 0인 경우)는 입력으로 주어지지 않음
- r은 커서의 최초 세로(행) 위치, c는 커서의 최초 가로(열) 위치
- 게임 화면의 좌측 상단이 (0, 0), 우측 하단이 (3, 3)
- 출력: 최소 키 조작 횟수
board | r | c | result |
[[1,0,0,3],[2,0,0,0],[0,0,0,2],[3,0,1,0]] | 1 | 0 | 14 |
[[3,0,0,2],[0,0,1,0],[0,1,0,0],[2,0,0,3]] | 0 | 1 | 16 |
풀이
- 서로 다른 카드를 뒤집게 되는 경우는 없다고 가정
- 카드 쌍의 각 좌표를 배열에 저장
- 카드의 종류가 최대 6가지이므로, 매 카드마다 제거하는 순서를 조합해서 최소 비용을 탐색
- 카드를 제거할 때 순서에 영향을 받으므로 "1번 카드 쌍 뒤집기 -> 2번 카드 쌍 뒤집기" 와 "2번 카드 쌍 뒤집기 -> 1번 카드 쌍 뒤집기" 의 경우는 다른 케이스로 생각해야 함
- 카드가 이미 제거되었는지는 정수형 변수에 1-bit를 저장해서 판단
- 제거되지 않은 카드 중에서 하나씩 제거하면서 비용을 계산하는데 두 카드 사이의 거리를 계산하는 함수(cost)를 DFS로 구현
- cost(pii& src, pii& dest, int visited) := src에서 dest로 이동할 때 최소 조작 횟수 반환
- visited는 현재 좌표의 방문 유무이며 4x4 배열이기 때문에 칸의 개수가 총 16개라 (1 << (y*4+x)) 번째 비트를 on/off 하여 파악할 수 있음
- 카드 뒤집기 연산은 포함하지 않고 이동만 포함
- dfs(pii cursor, int flipped, vector<pair<pii, pii>> loc) := 현재 커서의 위치 cursor와 현재 뒤집힌 카드 목록 flipped 와 각 카드의 좌표가 담긴 배열 loc 주어질 때 모두 뒤집을 때까지 필요한 최소 비용 반환
- 주의할 점: cost(src, dest, 0) 와 cost(dest, src, 0) 의 결과는 다를 수 있음
- 예를 들어, 아래와 같은 경우 라이언 카드는 위 함수의 호출 결과 각각 3과 2가 됨.
- 정답 코드
#include <string>
#include <vector>
#include <algorithm>
#define Y first
#define X second
using namespace std;
typedef pair<int,int> pii;
const int INF = 1e9;
const int d[5] = {0, -1, 0, 1, 0};
vector<vector<int>> B;
int cost(pii src, pii dest, int visited) {
if (src.Y == dest.Y and src.X == dest.X) return 0;
int ret = INF;
for (int i = 0; i < 4; i++) {
int ny = src.Y + d[i];
int nx = src.X + d[i+1];
if (ny < 0 or ny >= 4 or nx < 0 or nx >= 4 or (visited & (1 << (ny*4 + nx))))
continue;
// 1칸씩 이동
int nextVisited = visited | (1 << (src.Y*4 + src.X));
ret = min(ret, cost({ny, nx}, dest, nextVisited) + 1);
// 점프 이동
if (ny == src.Y) {
while (nx > 0 and nx < 3 and B[ny][nx] == 0) {
nextVisited |= (1 << (ny*4 + nx));
nx += d[i+1];
}
if (nx == src.X + d[i+1]) continue; // 1칸 이동한 것과 같으면 스킵
} else {
while (ny > 0 and ny < 3 and B[ny][nx] == 0) {
nextVisited |= (1 << (ny*4 + nx));
ny += d[i];
}
if (ny == src.Y + d[i]) continue; // 1칸 이동한 것과 같으면 스킵
}
ret = min(ret, cost({ny, nx}, dest, nextVisited) + 1);
}
return ret;
}
int dfs(pii cursor, int flipped, vector<pair<pii,pii>>& loc) {
int ret = INF;
for (int i = 0; i < 6; i++) {
// 이미 뒤집혀 제거되었거나 존재하지 않는 카드이면 continue
if ((flipped & (1 << i)) or loc[i].first.Y == -1) continue;
pii& src = loc[i].first;
pii& dest = loc[i].second;
// 현재 커서 위치에서 카드쌍의 각 위치까지 조작 비용을 계산해서 비교
int cost1 = cost(cursor, src, 0) + cost(src, dest, 0);
int cost2 = cost(cursor, dest, 0) + cost(dest, src, 0);
B[src.Y][src.X] = B[dest.Y][dest.X] = 0;
cost1 += dfs(dest, flipped | (1 << i), loc);
cost2 += dfs(src, flipped | (1 << i), loc);
ret = min(ret, min(cost1, cost2) + 2); // 2 = flipping cost
B[src.Y][src.X] = B[dest.Y][dest.X] = i+1;
}
return ret == INF ? 0 : ret;
}
int solution(vector<vector<int>> board, int r, int c) {
B = board;
vector<pair<pii,pii>> loc(6, {{-1, -1}, {-1, -1}});
for (int y = 0; y < 4; y++)
for (int x = 0; x < 4; x++)
if (board[y][x] > 0)
if (loc[board[y][x]-1].first.Y == -1)
loc[board[y][x]-1].first = {y, x};
else
loc[board[y][x]-1].second = {y, x};
return dfs({r, c}, 0, loc);
}
'Algorithm > Problem Solving' 카테고리의 다른 글
[BFS] (프로그래머스 Lv 3) 블록 이동하기 (0) | 2021.04.24 |
---|---|
[완전 탐색, DP] (프로그래머스 Lv 3) 외벽 점검 (0) | 2021.04.24 |
[구현] (프로그래머스 Lv 3) 기둥과 보 설치 (0) | 2021.04.24 |
[Floyd-Warshall] (프로그래머스 Lv 3) 합승 택시 요금 (0) | 2021.04.24 |
[SlidingWindow] (프로그래머스 Lv 3) 광고 삽입 (0) | 2021.04.24 |