문제
https://school.programmers.co.kr/learn/courses/30/lessons/60059
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
문제 분석
문제를 간단하게 요약하자면 N x N 의 자물쇠 이차원 배열과 M x M 의 열쇠 이차원 배열이 주어진다.
주어진 열쇠를 시계 방향으로 90도씩 회전하고 이동하면서 주어진 자물쇠를 풀 수 있는 지를 물어보는 문제이다.
열쇠의 돌기 부분이 자물쇠의 홈 부분에 딱 맞아야 자물쇠가 풀린다. (홈과 돌기는 1과 0으로 표기되어 있다.)
문제의 제한 사항을 확인해보면 N과 M이 3이상 20 이하이고 항상 열쇠 보다 자물쇠의 크기가 크거나 같다.
-> n이 충분히 작으니 완전탐색으로 풀 수 있지 않을까?
문제 풀이
해당 문제는 열쇠를 돌리고 이동하면서 가능한 모든 가능성을 체크해 보는 방법 밖에 없다고 생각했다.
위의 그림처럼 키를 회전시켜 가면서 자물쇠와 맞는 지 체크해보고 맞지 않으면 한 칸 이동해서 또 회전시켜가면서 맞는지 확인해 본다
이런 과정을 반복한다면 해당 문제를 풀 수 있을 것이다.
해당 문제를 풀기 위해서 아래와 같은 작업들이 필요할 것이다.
- 열쇠를 어떻게 이동 시킬까?
- 열쇠를 어떻게 회전 시킬까?
- 자물쇠가 풀린다는 것은 어떻게 알 수 있을까?
열쇠를 어떻게 이동시킬까?
나에게는 이 부분이 제일 골치 아팠다..
이중 배열 안에서 하나의 원소를 완전탐색하는 건 많이 해봤는데 이중 배열 안에서 이중 배열을 완전탐색 하려고 하니
어떻게 구현해야 할지 감이 안 왔다.
다른 자료들을 참고해 가면서 깨닫게 된 것은 기준을 하나 잡는 것이다.
열쇠 이중 배열의 첫 번째 원소를 기준으로 삼고, 이 기준을 옮기는 것이다.
기준의 위치를 알고 열쇠의 길이를 안다면 우린 열쇠의 다른 원소들의 위치도 알 수 있는 것이다.
잘 이해가 되지 않는다면 이따 코드를 통해 다시 이해해 보자
열쇠를 어떻게 회전시킬까?
그냥 이해하려고 한다면 어떻게 해야 할지 감이 오지 않는다.
위처럼 그림을 그려보면 어떻게 키를 회전시킬 수 있을지 알 수 있다.
회전시켜보면 다음과 같이 4가지의 경우가 나온다.
- 기본
- 기본에서 행과 열이 바뀌었다. 근데 바뀐 열이 끝에서부터 시작된다.
- 기본에서 행과 열은 유지하지만, 행이 끝에서 부터 시작한다.
- 기본에서 행과 열이 바뀌었고 행이 끝에서 부터 시작한다.
자물쇠가 풀린다는 것을 어떻게 알 수 있을까?
탐색할 때마다 자물쇠와 열쇠의 겹치는 부분을 더해준다. 만약 자물쇠의 모든 원소가 1이라면 자물쇠가 풀릴 것이다.
우린 자물쇠의 크기를 확장해 줄 것이다.
확장된 자물쇠의 길이 = 열쇠 길이 * 2 + 자물쇠 길이 - 2
- 탐색할 때 마다 확장된 자물쇠를 만들어 주고 확장된 자물쇠의 가운데에 기존 자물쇠 내용을 복사한다.
- 그리고 열쇠의 내용도 확장된 자물쇠에 더한다.
- 기존 자물쇠 부분만 내용을 확인하면서 모든 원소가 1인지 체크한다.
코드
public class LockAndKey {
public static void main(String[] args) {
int[][] key = {{0, 0, 0}, {1, 0, 0}, {0, 1, 1}};
int[][] lock = {{1, 1, 1}, {1, 1, 0}, {1, 0, 1}};
System.out.println(solution(key, lock));
}
public static boolean solution(int[][] key, int[][] lock) {
// 자물쇠의 기준
// 확장된 자물쇠에서 기존의 자물쇠 위치를 찾기 위해 필요
int offset = key.length - 1;
for (int r = 0; r < key.length + lock.length - 1; r++) {
for (int c = 0; c < key.length + lock.length - 1; c++) {
// 이 반복문을 통해 열쇠의 기준을 이동 시킨다.
for (int rotate = 0; rotate < 4; rotate++) {
// 4번 회전시키기 위한 반복문
// 확장된 자물쇠를 만든다.
int newLockSize = key.length * 2 + lock.length - 2;
int[][] newLock = new int[newLockSize][newLockSize];
for (int i = 0; i < lock.length; i++) {
for (int j = 0; j < lock.length; j++) {
newLock[i + offset][j + offset] = lock[i][j];
}
}
// newLock 에 해당 key 를 더한다.
match(newLock, key, r, c, rotate);
// 해당 키가 자물쇠를 열 수 있는지 확인한다.
if(check(newLock, offset, lock.length)){
return true;
}
}
}
}
return false;
}
public static void match(int[][] newLock, int[][] key, int row, int col, int rotate) {
int keyLen = key.length;
for (int i = 0; i < keyLen; i++) {
for (int j = 0; j < keyLen; j++) {
switch (rotate){
case 0:
newLock[i + row][j + col] += key[i][j];
break;
case 1:
newLock[i + row][j + col] += key[j][keyLen-1 - i];
break;
case 2:
newLock[i + row][j + col] += key[keyLen-1 - i][keyLen-1 - j];
break;
case 3:
newLock[i + row][j + col] += key[keyLen-1 - j][i];
break;
}
}
}
}
public static boolean check(int[][] newLock, int offset, int lockLength) {
for (int i = 0; i < lockLength; i++) {
for (int j = 0; j < lockLength; j++) {
if (newLock[i + offset][j + offset] != 1) {
return false;
}
}
}
return true;
}
}
배운 점
이중 배열 안에서 이중 배열을 어떻게 옮겨가면서 비교를 해야 할지 감이 잡히지 않았는데
이번 문제를 통해서 내가 이해할 수 있는 방식으로 정리할 수 있었다.
이중 배열의 기준을 옮긴다고 생각하면 된다.
기준의 위치에서 해당 배열의 길이를 알고 있다면 다른 원소들의 위치도 찾을 수 있다.
참고 자료
'코딩 테스트 > 프로그래머스' 카테고리의 다른 글
[프로그래머스] 두 큐 합 같게 만들기 (1) | 2024.03.07 |
---|---|
[프로그래머스] 파괴되지 않은 건물 (0) | 2024.03.07 |
[프로그래머스] 주차 요금 계산 (0) | 2024.03.07 |
[프로그래머스] 둘만의 암호 (0) | 2024.03.04 |
[프로그래머스] 디스크 컨트롤러 (0) | 2024.02.07 |