-
Notifications
You must be signed in to change notification settings - Fork 1
32-kangrae-jo #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
32-kangrae-jo #130
Conversation
|
과거의 저도 풀었었습니다 ㅎ 저거 푼다고 시간을 얼마나 버렸는지 기억도 안나네요.... |
kokeunho
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
삽질도 많이 하고 풀이 방향도 잘못 잡아 하루종일 걸렸습니다...
우선 빈칸과 조각의 정보를 Map 자료구조에
빈칸과 조각의 크기를 key로
빈칸과 조각의 경로 좌표 List를 value로 저장하였습니다.
빈칸 정보는 blanksInfo
조각 정보는 piecesInfo라 하겠습니다.
(이때 경로 좌표를 정규화 하지 않으면 풀리지 않더군요..
정규화를 해야하는 이유를 이해하는데 제일 오래 걸린 것 같습니다.)
그리고 빈칸과 조각의 경로를 비교하여 같은 모양일 시에
count하고 해당 빈칸과 조각을 blanksInfo와 piecesInfo에서 제거합니다.
그리고 piecesInfo를 시계방향으로 90도 회전 시킵니다.
이때 x, y 좌표값을 y, table 너비 - 1 - x로 계산하면 됩니다.
그리고 또 빈칸과 채우기를 시도합니다.
이를 과정을 조각이 시계방향으로 한바퀴 돌 때까지 총 4번 수행합니다.
수고하셨습니다~
Details
import java.util.*;
class Solution {
static int[] dx = {-1, 1, 0, 0};
static int[] dy = {0, 0, -1, 1};
public int solution(int[][] game_board, int[][] table) {
int answer = 0;
Map<Integer, List<List<int[]>>> blanksInfo = getPiecesInfo(game_board, 0);
Map<Integer, List<List<int[]>>> piecesInfo = getPiecesInfo(table, 1);
for (int i = 0; i < 4; i++) {
//blanksInfo와 piecesInfo 비교하여 일치하는 빈칸과 조각이 있다면
//game_board에서 채우고 두 빈칸과 조각 정보 Map에서 제거
//채운 빈칸 수(answer) 카운트
for (int size : blanksInfo.keySet()) {
if (!piecesInfo.containsKey(size)) continue;
List<List<int[]>> blanksList = blanksInfo.get(size);
List<List<int[]>> piecesList = piecesInfo.get(size);
Iterator<List<int[]>> blankIter = blanksList.iterator();
while (blankIter.hasNext()) {
List<int[]> blank = blankIter.next();
Iterator<List<int[]>> pieceIter = piecesList.iterator();
while (pieceIter.hasNext()) {
List<int[]> piece = pieceIter.next();
if (isSameShape(blank, piece)) {
answer += size;
blankIter.remove();
pieceIter.remove();
break;
}
}
}
}
piecesInfo = rotatePieces(piecesInfo, table.length);
}
return answer;
}
//게임 보드나 테이블의 빈칸, 조각 정보들을 Map<size : 경로, 경로>형식으로 반환
//targetNum은 game_board일 시 0, table일 시 1
public Map<Integer, List<List<int[]>>> getPiecesInfo(int[][] arr, int targetNum) {
Map<Integer, List<List<int[]>>> pieceInfo = new HashMap<>();
int width = arr.length;
boolean[][] visited = new boolean[width][width];
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
if (arr[i][j] == targetNum && !visited[i][j]) {
List<int[]> piecePath = dfs(i, j, arr, targetNum, visited);
piecePath = normalize(piecePath);
pieceInfo.putIfAbsent(piecePath.size(), new ArrayList<>());
pieceInfo.get(piecePath.size()).add(piecePath);
}
}
}
return pieceInfo;
}
//x, y에서 시작한 빈칸, 조각의 경로를 반환
public List<int[]> dfs(int x, int y, int[][] arr, int targetNum, boolean[][] visited) {
List<int[]> response = new ArrayList<>();
response.add(new int[]{x, y});
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < arr.length && ny >= 0 && ny < arr.length
&& !visited[nx][ny] && arr[nx][ny] == targetNum) {
response.addAll(dfs(nx, ny, arr, targetNum, visited));
}
}
return response;
}
// 조각 좌표를 좌상단 기준으로 이동 + 정렬
public List<int[]> normalize(List<int[]> piece) {
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
for (int[] p : piece) {
minX = Math.min(minX, p[0]);
minY = Math.min(minY, p[1]);
}
List<int[]> normalized = new ArrayList<>();
for (int[] p : piece) {
normalized.add(new int[]{p[0]-minX, p[1]-minY});
}
// x 먼저, y 다음으로 정렬
normalized.sort((a,b) -> a[0]==b[0]? a[1]-b[1] : a[0]-b[0]);
return normalized;
}
//조각들을 90도 회전
public Map<Integer, List<List<int[]>>> rotatePieces(Map<Integer, List<List<int[]>>> piecesInfo, int boardSize) {
Map<Integer, List<List<int[]>>> response = new HashMap<>();
for (int size : piecesInfo.keySet()) {
List<List<int[]>> newPiecesList = new ArrayList<>();
for (List<int[]> piece : piecesInfo.get(size)) {
List<int[]> newPiece = new ArrayList<>();
for (int[] pos : piece) {
int nx = pos[1];
int ny = boardSize -1 - pos[0];
newPiece.add(new int[]{nx, ny});
}
newPiece = normalize(newPiece);
newPiecesList.add(newPiece);
}
response.put(size, newPiecesList);
}
return response;
}
public boolean isSameShape(List<int[]> blank, List<int[]> piece) {
if (blank.size() != piece.size()) return false;
for (int i = 0; i < blank.size(); i++) {
int[] b = blank.get(i);
int[] p = piece.get(i);
if (b[0] != p[0] || b[1] != p[1]) return false;
}
return true;
}
}
wnsmir
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
전에 한번 풀었던 문제라 40분정도 소요되었습니다.
다시 풀어봐도 햇갈렸던 부분은 딱 세가지였는데
- 회전을 어떻게 처리했었는지
- 정규화를 어떻게 해줬는지
- 어떻게 인덱스가 안꼬이도록 일치한 블록을 제거해주었는지
입니다.
1-> 어차피 정규화를 해서 비교해줄 것이기 떄문에 도형의 중간을 기준으로 돌리는것이 아닌 도형자체를 다른 사분면으로 보내는 0,0 기준의 90도 회전 함수를 사용했습니다.
x, y라면 -y, x가 되는거죠!
2 -> 도형전체에서 가장작은 x좌표, y좌표를 하나씩 가져오고 그 점을 0,0으로 만들면, 어떤 도형이라도 그 도형이 들어가는 가장 작은 사각형을 만들고, 그 사각형속의 좌표만 비교해주게 됩니다. (비교할떄는 정렬포함)
3 ->사실 3번은 지난번 이문제를 풀면서 겪은 뒤로 늘 써오던 방법입니다.
반복문도중 리스트에서 원소를 지우게되면 다음 반복문때 인덱스가 꼬이게됩니다. 따라서 뒤로 정렬을 해준 뒤 뒤에서부터 지우면 됩니다. 물론 정렬이 안되거나 중간의 원소를 지워야 할 경우에는
(arr[i] = arr[-1]; arr.pop()) 이방식도 괜찮습니다.
from collections import deque
def solution(game_board, table):
N = len(table)
dx = [1, -1, 0, 0]
dy = [0, 0, 1, -1]
def bfs(x, y, grid, visited, flag):
# 게임보드일때
if flag == True:
num = 0
# 테이블일때
else:
num = 1
queue = deque()
queue.append((x, y))
block = []
block.append((x, y))
visited[x][y] = True
while queue:
x, y = queue.popleft()
for i in range(4):
nx = x + dx[i]
ny = y + dy[i]
if 0 <= nx < N and 0 <= ny < N:
if grid[nx][ny] == num and not visited[nx][ny]:
queue.append((nx, ny))
visited[nx][ny] = True
block.append((nx, ny))
# 블록 한조각 반환
return block
def normalize(blocks):
normalized_blocks = []
min_x = float('inf')
min_y = float('inf')
for block in blocks:
x, y = block
if min_x > x:
min_x = x
if min_y > y:
min_y = y
for block in blocks:
x, y = block
normalized_blocks.append((x - min_x, y - min_y))
return normalized_blocks
def rotate(blocks):
rotated_blocks = []
for block in blocks:
x, y = block
rotated_blocks.append((-y, x))
return rotated_blocks
# 정규화 + 정렬 + 튜플
def canon(blocks):
return tuple(sorted(normalize(blocks)))
table_visited = [[False]*N for _ in range(N)]
# table에 있는 조각들 blocks에 추가
blocks = []
for i in range(N):
for j in range(N):
if table[i][j] == 1 and not table_visited[i][j]:
blocks.append(bfs(i, j, table, table_visited, False))
answer = 0
# game_board 빈공간들에 정규화 + 회전한 조각들 대입
game_board_visited = [[False]*N for _ in range(N)]
for i in range(N):
for j in range(N):
if game_board[i][j] == 0 and not game_board_visited[i][j]:
blank_block = bfs(i, j, game_board, game_board_visited, True)
blank_canon = canon(blank_block)
for b in range(len(blocks) - 1, -1, -1):
block = blocks[b]
c0 = canon(block)
b90 = rotate(block)
c90 = canon(b90)
b180 = rotate(b90)
c180 = canon(b180)
b270 = rotate(b180)
c270 = canon(b270)
if blank_canon in (c0, c90, c180, c270):
answer += len(block)
blocks.pop(b)
break
return answer
g0rnn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어젯 밤부터 머리를 쥐어 짜내면서 풀었습니다 ^^; 그런데도 너무 어려워서 답? 힌트?를 두번이나 봤네요..
가장 어려웠던건 퍼즐 조각과 빈 공간을 어떻게 비교해야하는지 였습니다.
그래서 좀 찾아보니
1. 정렬
2. 좌표 직접 비교
를 하면 두 조각을 비교할 수 있습니다! 지금 생각해보면 꽤 간단한 아이디어인데 왜이렇게나 어려웠는지 모르겠네요..
강래님의 코드를 보니 비어있는 공간을 모두 담고 있더라고요. 그래서 저도 빈 공간과 퍼즐조각을 모두 담아서 이중 for문으로 일일이 살펴보며 비슷하게 구현했습니다.
normalize와 rotate도 직관적으로 떠오르지 않아 힌트를 봤는데요.. 그럼에도 많은 시간을 썻네요..ㅜㅜ
- 정규화는 간단하게 가장 작은 x, y를 찾아 모든 좌푯값에서 빼주면 되었고,
- 회전은 (x, y) 을 90도 -> (y, -x) 로 바꾸면 됩니다.
디테일한 구현은 제 코드에서 확인하기실 바랍니다.!!
java
import java.util.*;
class Solution {
private int size;
private List<List<int[]>> empty = new ArrayList<>();
private List<List<int[]>> puzzles = new ArrayList<>();
private int[][] offset = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
public int solution(int[][] game_board, int[][] table) {
size = table.length;
boolean[][] eVisited = new boolean[size][size];
boolean[][] pVisited = new boolean[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (!eVisited[i][j] && game_board[i][j] == 0) {
empty.add(extractShape(i, j, game_board, eVisited, 0));
}
if (!pVisited[i][j] && table[i][j] == 1) {
puzzles.add(extractShape(i, j, table, pVisited, 1));
}
}
}
return match();
}
private int match() {
int maxSize = puzzles.size();
boolean[] used = new boolean[maxSize];
int cnt = 0;
for (List<int[]> shape : empty) {
for (int i = 0; i < maxSize; i++) {
if (used[i]) continue;
List<int[]> puzzle = puzzles.get(i);
if (matchRotatedPuzzles(shape, puzzle)) {
cnt += puzzle.size();
used[i] = true;
break;
}
}
}
return cnt;
}
private boolean matchRotatedPuzzles(List<int[]> shape, List<int[]> puzzle) {
for (int i = 0; i < 4; i++) {
if (compare(shape, puzzle)) return true;
puzzle = rotate(puzzle);
}
return false;
}
// 왼쪽으로 90도만 회전시킴
private List<int[]> rotate(List<int[]> puzzle) {
List<int[]> rotated = new ArrayList<>();
int minx = Integer.MAX_VALUE;
int miny = Integer.MAX_VALUE;
for (int[] coor : puzzle) {
minx = Math.min(minx, coor[0]);
miny = Math.min(miny, coor[1]);
}
for (int[] coor : puzzle) {
rotated.add(new int[]{coor[1] - minx, -1 * coor[0] - miny});
}
normalize(rotated);
return rotated;
}
private boolean compare(List<int[]> shape, List<int[]> puzzle) {
if (shape.size() != puzzle.size()) return false;
for (int i = 0; i < shape.size(); i++) {
if (shape.get(i)[0] != puzzle.get(i)[0]) return false;
if (shape.get(i)[1] != puzzle.get(i)[1]) return false;
}
return true;
}
private List<int[]> extractShape(int i, int j, int[][] board, boolean[][] visited, int SHAPE) {
List<int[]> shape = new ArrayList<>();
Deque<int[]> dq = new ArrayDeque<>();
visited[i][j] = true;
dq.add(new int[]{j, i});
shape.add(new int[]{j, i});
while (!dq.isEmpty()) {
int x = dq.peek()[0];
int y = dq.poll()[1];
for (int[] o : offset) {
int nx = x + o[0];
int ny = y + o[1];
if (nx < 0 || ny < 0 || nx >= size || ny >= size) continue;
if (visited[ny][nx]) continue;
if (board[ny][nx] == SHAPE) {
dq.add(new int[]{nx, ny});
visited[ny][nx] = true;
shape.add(new int[]{nx, ny});
}
}
}
normalize(shape);
return shape;
}
private void normalize(List<int[]> shape) {
shape.sort((a, b) -> {
if (a[0] == b[0]) return Integer.compare(a[1], b[1]);
return Integer.compare(a[0], b[0]);
});
int minx = Integer.MAX_VALUE;
int miny = Integer.MAX_VALUE;
for (int[] coor : shape) {
minx = Math.min(coor[0], minx);
miny = Math.min(coor[1], miny);
}
for (int[] coor : shape) {
coor[0] -= minx;
coor[1] -= miny;
}
}
}|
균호님 코드 확인하고 제가 머지하겠습니다. |
🔗 문제 링크
[퍼즐 조각 채우기]
✔️ 소요된 시간
2h..
✨ 수도 코드
이 문제의 풀이는 다음과 같아요.
아마 풀이법은 쉽게 떠올리셨을겁니다.
그런데 구현이 상당히 어렵습니다.
그래서 2시간이나 걸렸네요.
함수명이나 일부 함수 구조가 엉망입니다.
(하지만 구현문제에서는 중요하지않다고 생각합니다....)
제 풀이의 특이한 점은 회전을 일찍 적용한다는 점입니다.
테이블에서 퍼즐 조각을 떼어내서,
게임판의 빈곳과 대치시켜볼때 회전을 시키기 위해서는
테이블의 퍼즐 조각을 떼어낼때 n*n의 정사각형 배열에 담아야한다는 점이 불편했기 때문입니다.
그래서 저는 이미 정사각현인 테이블을 돌려가며 퍼즐조각을 떼어내는 방법을 생각해냈습니다.
그러면 모양 하나당 최대 4개의 모습을 가질 수 있는데, 그것들을 다 저장할 수 있는 것이죠.
그 뒤에는...!
모든 조각을 대치시켜보면 되는 것이죠.
막막해보이지만 도전해보시길 바랍니다.
📚 새롭게 알게된 내용