[알고리즘 풀이 분석] 프로그래머스 자물쇠와 열쇠 (2020 Kakao Blind Recruitment)

nnnyeong·2021년 8월 29일
0

알고리즘 풀이분석

목록 보기
43/101

오늘 풀어본 두번째 문제는 프로그래머스 자물쇠와 열쇠 이다. 역시나 2020 카카오 공채 기출문제이고 Level 3의 시뮬레이션 문제이다.




프로그래머스 자물쇠와 열쇠

고고학자인 "튜브"는 고대 유적지에서 보물과 유적이 가득할 것으로 추정되는 비밀의 문을 발견하였습니다. 그런데 문을 열려고 살펴보니 특이한 형태의 자물쇠로 잠겨 있었고 문 앞에는 특이한 형태의 열쇠와 함께 자물쇠를 푸는 방법에 대해 다음과 같이 설명해 주는 종이가 발견되었습니다.

잠겨있는 자물쇠는 격자 한 칸의 크기가 1 x 1인 N x N 크기의 정사각 격자 형태이고 특이한 모양의 열쇠는 M x M 크기인 정사각 격자 형태로 되어 있습니다.

자물쇠에는 홈이 파여 있고 열쇠 또한 홈과 돌기 부분이 있습니다. 열쇠는 회전과 이동이 가능하며 열쇠의 돌기 부분을 자물쇠의 홈 부분에 딱 맞게 채우면 자물쇠가 열리게 되는 구조입니다. 자물쇠 영역을 벗어난 부분에 있는 열쇠의 홈과 돌기는 자물쇠를 여는 데 영향을 주지 않지만, 자물쇠 영역 내에서는 열쇠의 돌기 부분과 자물쇠의 홈 부분이 정확히 일치해야 하며 열쇠의 돌기와 자물쇠의 돌기가 만나서는 안됩니다. 또한 자물쇠의 모든 홈을 채워 비어있는 곳이 없어야 자물쇠를 열 수 있습니다.

열쇠를 나타내는 2차원 배열 key와 자물쇠를 나타내는 2차원 배열 lock이 매개변수로 주어질 때, 열쇠로 자물쇠를 열수 있으면 true를, 열 수 없으면 false를 return 하도록 solution 함수를 완성해주세요.




입출력

  • key는 M x M(3 ≤ M ≤ 20, M은 자연수)크기 2차원 배열입니다.
  • lock은 N x N(3 ≤ N ≤ 20, N은 자연수)크기 2차원 배열입니다.
  • M은 항상 N 이하입니다.
  • key와 lock의 원소는 0 또는 1로 이루어져 있습니다.
  • 0은 홈 부분, 1은 돌기 부분을 나타냅니다.

keylockresult
[[0, 0, 0], [1, 0, 0], [0, 1, 1]][[1, 1, 1], [1, 1, 0], [1, 0, 1]]true



나의 풀이

문제를 읽고 본격적으로 풀기 전에 떠오르는 방법들을 생각 해 보았다.

가장 기본적으로 자물쇠를 기준으로 열쇠를 돌리고 옮겨 가며 모든 경우를 대상으로 가능한지를 확인하는 단순한 방법이 있을테고, 시간초과가 날지 모르니 좀더 범위를 좁히고, 최소한의 경우만 생각하여 확인하는 방법이 있지 않을까 했다.

하지만 문제에서 제시되는 자물쇠와 열쇠의 크기가 최대 20*20 짜리 2차원 배열이었기에 기본적인 방법으로 풀어도 될 것 같아 선택하였다.

또 문제가 특별한 알고리즘을 생각해 내야 하기 보단 조금 난이도 있는 시뮬레이션 문제라고 느꼈기 때문에 모든 열쇠를 4 방향으로 돌리고 옮겨가면서 확인하는 방법을 선택하였다.

가장 어려운 부분은 열쇠를 돌리는 규칙을 찾는 것이었다.
지난번 BOJ 15685 드래곤 커브 문제를 풀 때에도 느낀 것이지만 배열 이동 혹은 회전 시의 좌표 변화의 규칙성을 찾는 부분이 약한 것 같다.

이 부분은 질문 리스트 중 한 분의 글의 도움을 받아 규칙을 발견하였고 3*3 배열을 기준으로 9개의 좌표를 모두 나열했을 때 열쇠를 돌려 (y1,x1) -> (y2, x2) 변환시 y2 = x1, x2 = N-1-y1 의 규칙이 있었다.

또 두번째로 시간을 투자한 부분은 자물쇠 기준으로 열쇠를 옮겨 다닐 때 자물쇠 좌표가 열쇠의 좌표로 어떻게 변환 되는지였다. 자물쇠의 (i,j) 를 확인 할 때 해당 시점에 열쇠 위치 기준 변화량을 dy, dx (-N+1 ~ M+1) 먼저 확인하고 이 때 M * M 크기의 열쇠 배열 내 좌표는 (i + dy, j + dx) 로 변환하였다.

또 문제 내용 중 열쇠의 돌기와 자물쇠의 돌기가 만나서는 안된다는 점도 주의해서 처리 해 주어야 했다!!




코드

#include <iostream>
#include <vector>

using namespace std;

int N, M;

// key 회전 시키기
void rotateKey(vector<vector<int>> & key){
    vector<vector<int>> newKey(M, vector<int>(M, 0));

    for (int i = 0; i < M; ++i) {
        for (int j = 0; j<M; j++){
            int originy = M-1-j;
            int originx = i;
            newKey[i][j] = key[originy][originx];
        }
    }

    key = newKey;
}

bool solution(vector<vector<int>> key, vector<vector<int>> lock) {
    int rotation = 0;
    N = lock.size();
    M = key.size();

    // 자물쇠의 홀 갯수 count
    int hole = 0;
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) {
            if (lock[i][j] == 0) hole++;
        }
    }

    while (rotation < 4){
        if (rotation > 0) rotateKey(key);  // 3 번 열쇠 돌리기 

        for (int dy = M-1; dy >= -N+1 ; --dy) {
            for (int dx = M-1; dx >= -N+1 ; --dx) {
                // dy, dx = 열쇠와 자물쇠가 겹치도록 열쇠를 이동시킬 때 좌표 변화량 
                int count = 0;
                bool fail = false;
                for (int i = 0; i < N; ++i) {
                    for (int j = 0; j < N; ++j) {
                        // 자물쇠의 i, j 좌표와 겹치는 열쇠 좌표 구하기 
                        int keyY = i + dy, keyX = j + dx;
                        if (keyY>=0 && keyY<M && keyX>=0 && keyX<M && key[keyY][keyX] == 1) { // 열쇠의 돌기 부분 
                            if (lock[i][j] == 0) count++;  // 채워지는 부분 count 
                            else {  // 열쇠 돌기, 지물쇠의 돌기가 만날 수 없게 
                                fail = true;
                                i = N;
                                j = N;
                            }
                        }
                    }
                }
                if (!fail && count==hole) return true;
            }
        }
        rotation++;
    }

    return false;
}
profile
주니어 개발자까지 ☄️☄️

0개의 댓글