Skip to content

Commit f8f1381

Browse files
committed
[level 3] Title: 카드 짝 맞추기, Time: 453.79 ms, Memory: 18.5 MB -BaekjoonHub
1 parent 8a2a2bf commit f8f1381

File tree

2 files changed

+273
-0
lines changed

2 files changed

+273
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# [level 3] 카드 짝 맞추기 - 72415
2+
3+
[문제 링크](https://school.programmers.co.kr/learn/courses/30/lessons/72415?language=swift)
4+
5+
### 성능 요약
6+
7+
메모리: 18.5 MB, 시간: 453.79 ms
8+
9+
### 구분
10+
11+
코딩테스트 연습 > 2021 KAKAO BLIND RECRUITMENT
12+
13+
### 채점결과
14+
15+
정확성: 100.0<br/>합계: 100.0 / 100.0
16+
17+
### 제출 일자
18+
19+
2025년 02월 21일 21:40:33
20+
21+
### 문제 설명
22+
23+
<p>게임 개발자인 <code>베로니</code>는 개발 연습을 위해 다음과 같은 간단한 카드 짝맞추기 보드 게임을 개발해 보려고 합니다.<br>
24+
게임이 시작되면 화면에는 카드 16장이 뒷면을 위로하여 <code>4 x 4</code> 크기의 격자 형태로 표시되어 있습니다. 각 카드의 앞면에는 카카오프렌즈 캐릭터 그림이 그려져 있으며, 8가지의 캐릭터 그림이 그려진 카드가 각기 2장씩 화면에 무작위로 배치되어 있습니다.<br>
25+
유저가 카드를 2장 선택하여 앞면으로 뒤집었을 때 같은 그림이 그려진 카드면 해당 카드는 게임 화면에서 사라지며, 같은 그림이 아니라면 원래 상태로 뒷면이 보이도록 뒤집힙니다. 이와 같은 방법으로 모든 카드를 화면에서 사라지게 하면 게임이 종료됩니다.</p>
26+
27+
<p>게임에서 카드를 선택하는 방법은 다음과 같습니다.</p>
28+
29+
<ul>
30+
<li>카드는 <code>커서</code>를 이용해서 선택할 수 있습니다.
31+
32+
<ul>
33+
<li>커서는 4 x 4 화면에서 유저가 선택한 현재 위치를 표시하는 "굵고 빨간 테두리 상자"를 의미합니다.</li>
34+
</ul></li>
35+
<li>커서는 [Ctrl] 키와 방향키에 의해 이동되며 키 조작법은 다음과 같습니다.
36+
37+
<ul>
38+
<li>방향키 ←, ↑, ↓, → 중 하나를 누르면, 커서가 누른 키 방향으로 1칸 이동합니다.</li>
39+
<li>[Ctrl] 키를 누른 상태에서 방향키 ←, ↑, ↓, → 중 하나를 누르면, 누른 키 방향에 있는 가장 가까운 카드로 한번에 이동합니다.
40+
41+
<ul>
42+
<li>만약, 해당 방향에 카드가 하나도 없다면 그 방향의 가장 마지막 칸으로 이동합니다.</li>
43+
</ul></li>
44+
<li>만약, 누른 키 방향으로 이동 가능한 카드 또는 빈 공간이 없어 이동할 수 없다면 커서는 움직이지 않습니다.</li>
45+
</ul></li>
46+
<li>커서가 위치한 카드를 뒤집기 위해서는 [Enter] 키를 입력합니다.
47+
48+
<ul>
49+
<li>[Enter] 키를 입력해서 카드를 뒤집었을 때
50+
51+
<ul>
52+
<li>앞면이 보이는 카드가 1장 뿐이라면 그림을 맞출 수 없으므로 두번째 카드를 뒤집을 때 까지 앞면을 유지합니다.</li>
53+
<li>앞면이 보이는 카드가 2장이 된 경우, 두개의 카드에 그려진 그림이 같으면 해당 카드들이 화면에서 사라지며, 그림이 다르다면 두 카드 모두 뒷면이 보이도록 다시 뒤집힙니다.</li>
54+
</ul></li>
55+
</ul></li>
56+
</ul>
57+
58+
<p>"베로니"는 게임 진행 중 카드의 짝을 맞춰 몇 장 제거된 상태에서 카드 앞면의 그림을 알고 있다면, 남은 카드를 모두 제거하는데 필요한 키 조작 횟수의 최솟값을 구해 보려고 합니다. 키 조작 횟수는 방향키와 [Enter] 키를 누르는 동작을 각각 조작 횟수 <code>1</code>로 계산하며, [Ctrl] 키와 방향키를 함께 누르는 동작 또한 조작 횟수 <code>1</code>로 계산합니다.</p>
59+
60+
<p>다음은 카드가 몇 장 제거된 상태의 게임 화면에서 커서를 이동하는 예시입니다.<br>
61+
아래 그림에서 빈 칸은 이미 카드가 제거되어 없어진 칸을 의미하며, 그림이 그려진 칸은 카드 앞 면에 그려진 그림을 나타냅니다.</p>
62+
63+
<p><img src="https://grepp-programmers.s3.ap-northeast-2.amazonaws.com/files/production/bd1c06b3-6684-480a-85e6-53f1123b0770/2021_kakao_card_01.png" title="" alt="2021_kakao_card_01.png"><br>
64+
예시에서 커서는 두번째 행, 첫번째 열 위치에서 시작하였습니다.<br>
65+
<img src="https://grepp-programmers.s3.ap-northeast-2.amazonaws.com/files/production/8d9008a0-a933-44c7-92a8-96b701483d6e/2021_kakao_card_02.png" title="" alt="2021_kakao_card_02.png"><br>
66+
[Enter] 입력, ↓ 이동, [Ctrl]+→ 이동, [Enter] 입력 = 키 조작 4회<br>
67+
<img src="https://grepp-programmers.s3.ap-northeast-2.amazonaws.com/files/production/89b256d7-b8a8-4fb1-a1f4-84407a029d03/2021_kakao_card_03.png" title="" alt="2021_kakao_card_03.png"><br>
68+
[Ctrl]+↑ 이동, [Enter] 입력, [Ctrl]+← 이동, [Ctrl]+↓ 이동, [Enter] 입력 = 키 조작 5회<br>
69+
<img src="https://grepp-programmers.s3.ap-northeast-2.amazonaws.com/files/production/96b37dbd-bba1-47e0-89e5-7a3e518eab24/2021_kakao_card_04.png" title="" alt="2021_kakao_card_04.png"><br>
70+
[Ctrl]+→ 이동, [Enter] 입력, [Ctrl]+↑ 이동, [Ctrl]+← 이동, [Enter] 입력 = 키 조작 5회</p>
71+
72+
<p>위와 같은 방법으로 커서를 이동하여 카드를 선택하고 그림을 맞추어 카드를 모두 제거하기 위해서는 총 14번(방향 이동 8번, [Enter] 키 입력 6번)의 키 조작 횟수가 필요합니다.</p>
73+
74+
<hr>
75+
76+
<h4><strong>[문제]</strong></h4>
77+
78+
<p>현재 카드가 놓인 상태를 나타내는 2차원 배열 board와 커서의 처음 위치 r, c가 매개변수로 주어질 때, 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.</p>
79+
80+
<h4><strong>[제한사항]</strong></h4>
81+
82+
<ul>
83+
<li>board는 4 x 4 크기의 2차원 배열입니다.</li>
84+
<li>board 배열의 각 원소는 0 이상 6 이하인 자연수입니다.
85+
86+
<ul>
87+
<li>0은 카드가 제거된 빈 칸을 나타냅니다.</li>
88+
<li>1 부터 6까지의 자연수는 2개씩 들어있으며 같은 숫자는 같은 그림의 카드를 의미합니다.</li>
89+
<li>뒤집을 카드가 없는 경우(board의 모든 원소가 0인 경우)는 입력으로 주어지지 않습니다.</li>
90+
</ul></li>
91+
<li>r은 커서의 최초 세로(행) 위치를 의미합니다.</li>
92+
<li>c는 커서의 최초 가로(열) 위치를 의미합니다.</li>
93+
<li>r과 c는 0 이상 3 이하인 정수입니다.</li>
94+
<li>게임 화면의 좌측 상단이 (0, 0), 우측 하단이 (3, 3) 입니다.</li>
95+
</ul>
96+
97+
<hr>
98+
99+
<h5><strong>[입출력 예]</strong></h5>
100+
<table class="table">
101+
<thead><tr>
102+
<th>board</th>
103+
<th>r</th>
104+
<th>c</th>
105+
<th>result</th>
106+
</tr>
107+
</thead>
108+
<tbody><tr>
109+
<td>[[1,0,0,3],[2,0,0,0],[0,0,0,2],[3,0,1,0]]</td>
110+
<td>1</td>
111+
<td>0</td>
112+
<td>14</td>
113+
</tr>
114+
<tr>
115+
<td>[[3,0,0,2],[0,0,1,0],[0,1,0,0],[2,0,0,3]]</td>
116+
<td>0</td>
117+
<td>1</td>
118+
<td>16</td>
119+
</tr>
120+
</tbody>
121+
</table>
122+
<h5><strong>입출력 예에 대한 설명</strong></h5>
123+
124+
<hr>
125+
126+
<p><strong>입출력 예 #1</strong><br>
127+
문제의 예시와 같습니다.</p>
128+
129+
<p><strong>입출력 예 #2</strong><br>
130+
입력으로 주어진 게임 화면은 아래 그림과 같습니다.</p>
131+
132+
<p><img src="https://grepp-programmers.s3.ap-northeast-2.amazonaws.com/files/production/5c6e8d3f-2427-42b8-893b-5677cb45aa5d/2021_kakao_card_05.png" title="" alt="2021_kakao_card_05.png"></p>
133+
134+
<p>위 게임 화면에서 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값은 16번 입니다.</p>
135+
136+
137+
> 출처: 프로그래머스 코딩 테스트 연습, https://school.programmers.co.kr/learn/challenges
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import Foundation
2+
3+
let dir:[[Int]] = [[0,1], [0,-1], [1,0], [-1,0]]
4+
var visit:Set<Status> = []
5+
6+
struct Card:Hashable {
7+
let r:Int
8+
let c:Int
9+
let num:Int
10+
}
11+
12+
struct Status:Hashable{
13+
let board:[[Int]]
14+
let r:Int
15+
let c:Int
16+
let openCard:Card?
17+
18+
init (_ board:[[Int]], _ r:Int, _ c:Int, _ openCard:Card?) {
19+
self.board = board
20+
self.r = r
21+
self.c = c
22+
self.openCard = openCard
23+
}
24+
}
25+
26+
func isAllCardOpened(_ cBoard:[[Int]]) -> Bool {
27+
for card in Array(cBoard.flatMap{$0}) {
28+
if card != 0 {
29+
return false
30+
}
31+
}
32+
33+
return true
34+
}
35+
36+
func isOnEnd(_ r:Int, _ c:Int, _ dir:Int) -> Bool {
37+
if (dir == 0 && c == 3) ||
38+
(dir == 1 && c == 0) ||
39+
(dir == 2 && r == 3) ||
40+
(dir == 3 && r == 0) {
41+
return true
42+
} else {
43+
return false
44+
}
45+
}
46+
47+
func solution(_ board:[[Int]], _ r:Int, _ c:Int) -> Int {
48+
var queue:[(status:Status, moveCnt:Int)] = []
49+
50+
queue.append((Status(board,r,c,nil),0))
51+
visit.insert(Status(board,r,c,nil))
52+
53+
while !queue.isEmpty {
54+
let q = queue.removeFirst()
55+
56+
var cBoard:[[Int]] = q.status.board
57+
let cr:Int = q.status.r
58+
let cc:Int = q.status.c
59+
let openCard:Card? = q.status.openCard
60+
let moveCnt:Int = q.moveCnt
61+
62+
// 방향키
63+
for d in dir {
64+
let nr:Int = cr + d[0]
65+
let nc:Int = cc + d[1]
66+
if nr < 0 || nr > 3 || nc < 0 || nc > 3 {
67+
continue
68+
} else if visit.contains(Status(cBoard,nr,nc,openCard)) {
69+
continue
70+
} else {
71+
queue.append((Status(cBoard,nr,nc,openCard), moveCnt+1))
72+
visit.insert(Status(cBoard,nr,nc,openCard))
73+
}
74+
}
75+
76+
// ctrl + 방향키
77+
for (i,d) in dir.enumerated() {
78+
79+
var nr:Int = cr + d[0]
80+
var nc:Int = cc + d[1]
81+
82+
if nr < 0 || nr > 3 || nc < 0 || nc > 3 {
83+
continue
84+
}
85+
86+
while cBoard[nr][nc] == 0 && !isOnEnd(nr,nc,i) {
87+
nr = nr + d[0]
88+
nc = nc + d[1]
89+
}
90+
91+
if visit.contains(Status(cBoard,nr,nc,openCard)) {
92+
continue
93+
} else {
94+
queue.append((Status(cBoard,nr,nc,openCard), moveCnt+1))
95+
visit.insert(Status(cBoard,nr,nc,openCard))
96+
}
97+
}
98+
99+
// open
100+
// 짝을 맞출 카드가 있을 경우
101+
if let openedCard = openCard {
102+
// 짝 맞음
103+
if openedCard.num == cBoard[cr][cc] && !(openedCard.r == cr && openedCard.c == cc) {
104+
cBoard[openedCard.r][openedCard.c] = 0
105+
cBoard[cr][cc] = 0
106+
107+
if isAllCardOpened(cBoard) {
108+
return moveCnt+1
109+
}
110+
111+
visit.insert(Status(cBoard,cr,cc,nil))
112+
queue.append((Status(cBoard,cr,cc,nil),moveCnt+1))
113+
}
114+
// 짝 틀림
115+
else {
116+
if visit.contains(Status(cBoard,cr,cc,openCard)) {
117+
continue
118+
} else {
119+
queue.append((Status(cBoard,cr,cc,nil), moveCnt+1))
120+
visit.insert(Status(cBoard,cr,cc,nil))
121+
}
122+
}
123+
}
124+
// 새로 카드를 오픈할 때
125+
else {
126+
let newOpenCard = Card(r:cr, c:cc, num:cBoard[cr][cc])
127+
128+
if visit.contains(Status(cBoard,cr,cc,newOpenCard)) {
129+
continue
130+
}
131+
queue.append((Status(cBoard,cr,cc,newOpenCard), moveCnt+1))
132+
visit.insert(Status(cBoard,cr,cc,newOpenCard))
133+
}
134+
}
135+
return Int.max
136+
}

0 commit comments

Comments
 (0)