-
[CodeKata] 프로그래머스 : 5.1(토), 자물쇠와 열쇠Algorithm 2021. 5. 1. 22:58반응형
🥋 Oooth More!! (Level 3)
* 풀이 강의 링크 : programmers.co.kr/learn/courses/10336?utm_source=programmers&utm_medium=test_course10336&utm_campaign=course_10336
key를 90도씩 돌려, key의 돌기들로 Lock의 홈을 채울 수 있으면 된다. 단, key의 돌기가 Lock의 돌기와 만나는 경우가 있으면 안된다.
🧮 풀이
function rotateArr(originalArr) { const N = originalArr.length; const rotatedArr = Array.from(Array(N), () => new Array(N).fill(null)) for (let row = 0; row < N; row++) { for (let col = 0; col < N; col++) { rotatedArr[col][N - 1 - row] = originalArr[row][col] } } return rotatedArr; } function solution(key, lock) { const offset = key.length - 1; const keyBumps = []; let rotateCount = 0; while (rotateCount < 4) { let nowBump = []; for (let x = 0 ; x < key.length ; x++) { for (let y = 0 ; y < key.length ; y++) { if (key[x][y] === 1) nowBump.push([x,y]); } } keyBumps.push(nowBump); key = rotateArr(key); rotateCount++; } for (let i = -offset ; i < lock.length + offset ; i++) { for (let j = -offset ; j < lock.length + offset ; j++) { for (let bumps of keyBumps) { let nowLock = JSON.parse(JSON.stringify(lock)); bumps.forEach(bump => { if (i+bump[0] >= 0 && i+bump[0] < lock.length && j+bump[1] >= 0 && j+bump[1] < lock.length) { nowLock[i+bump[0]][j+bump[1]] += 1; } }) if (nowLock.flat().every(v => v === 1)) { return true; } } } } return false; }
- rotateArr() 2차원 배열 회전 함수
- 이 함수는, originalArr 2차원 배열을 매개변수로 받아, rotatedArr 회전한 2차원 배열을 반환한다. 크기는 변함없이 N * N.
- N과 Array.from() 메서드로 N * N의 빈 2차원 배열을 만들어준다.
- 이 배열을 2중순회를 통해 각각 채워준다. 이 떄, 내부의 로직에 따라 회전하는 방향을 정할 수 있다. (위 예시는 시계방향 90도)
- key를 회전한 4가지 경우의 돌기 좌표들로 변형
- 우선, 입력된 key를 통해 회전한 4가지 경우의 key 형태를 구해야겠다고 생각했다. rotateCount가 회전시키는 횟수이다. (총 3회)
- 또한, key는 돌기만 필요하므로 돌기들의 좌표만 필요하다. 이를, 2중순회를 통해 nowBump 배열에 튜플들로 누적한다.
- 마지막으로, 이 nowBump를 keyBumps 배열에 누적한다. 이어서, key를 rotateArr() 함수로 회전, rotateCount에 1을 더한다.
- lock에 key를 맞춰보기
- lock을 순회하며 key를 맞춰본다. 이 때, [0,0] 부터가 아닌, key의 오른쪽 아래 모서리가 lock의 왼쪽 윗 모서리와 맞닿는 경우부터 시작해야한다. 종료 역시 key의 왼쪽 윗 모서리가 lock의 오른쪽 아래 모서리와 맞닿는 경우다. 그렇기에 순회기준에 offset 값을 준다.
- 각 위치에서 keyBumps(key가 회전하는 4가지 경우의 돌기)들을 고려한다. 이는, lock의 사본(깊은 복사)인 nowLock에 적용.
- 현재 key인 bumps를 순회하며 nowLock의 [i + bump[0], j + bump[1]] 에 1을 더해준다. lock의 범위를 벗어나면 오류가 발생하므로 if 조건문을 먼저 실행한 것이다.
- 마지막으로, nowLock을 flat()을 통해 1차원 배열로 만든 뒤, 여기에 1 이외의 값이 없다면 true를 반환한다.
- 1 이외의 값이 있는 경우는 2가지다. 0은 lock의 홈을 다 못채운 경우, 2는 lock 돌기와 key 돌기가 겹쳤던 경우이다.
🖇 리뷰
function rotate90(matrix) { const length = matrix.length; const rotated = new Array(length) .fill(null) .map(() => new Array(length).fill(null)); for (let col = 0; col < length; col++) { for (let row = length - 1; row >= 0; row--) { rotated[col][length - 1 - row] = matrix[row][col]; } } return rotated; } function makeBoard(origin) { const length = origin.length; const board = new Array(length * 3) .fill(null) .map(() => new Array(length * 3).fill(0)); for (let m = 0; m < length; m++) { for (let n = 0; n < length; n++) { board[m + length][n + length] = origin[m][n]; } } return board; } function matchKey(rowStart, colStart, board, key) { const length = key.length; for (let i = 0; i < length; i++) { for (let j = 0; j < length; j++) { board[rowStart + i][colStart + j] += key[i][j]; } } } function isMatched(board) { const length = board.length; for (let i = length / 3; i < (length / 3) * 2; i++) { for (let j = length / 3; j < (length / 3) * 2; j++) { if (board[i][j] != 1) { return false; } } } return true; } function solution(key, lock) { const length = lock.length; for (let i = 0; i < 4; i++) { key = rotate90(key); for (let j = 0; j <= length * 2; j++) { for (let k = 0; k <= length * 2; k++) { const board = makeBoard(lock); matchKey(j, k, board, key); if (isMatched(board)) { return true; } } } } return false; }
우선, 카카오의 해설이다. 코드는 길지만 컨셉을 얘기하면, lock을 3 * 3으로 복제한 board 2차원 배열을 순회하는 방법이다.
rotate90() 함수는 풀이와 똑같다. 시계방향으로 90도 회전시키는 함수이며, solution() 에서도 4번동안 key를 회전시킨다.
makeBoard() 함수가 3 * 3 lock을 만드는 함수다. key가 lock을 벗어나는 경우를 위함인 것 같다.
matchKey() 함수는 시작점(x,y), board, key를 매개변수로 받으며, 시작점부터 board의 값들에 key 값을 더해준다.
isMatched() 함수가 위에서 수정된 board가 1 이외의 값을 갖는지 여부를 탐색한다.
solution에서 lock 길이의 2배까지만 탐색하는 이유는, 그 이후의 값은 matchKey에서 key.length 만큼 추가적으로 작업되기 때문이다.
효율성 면에서 내 풀이 대비 엄청난 차이를 보이진 않았다. 둘 다 결론적으론 key가 lock을 크게 도는 원리이기 때문일 것이다.
다만, 나는 lock의 3 * 3이 아닌 바깥 라인만 타기 때문에 어떤 면에서는 내 풀이가 좀 더 효율적으로 나온 것이 아닌가 추정된다.
카카오 풀이를 보며 기능별 함수분할(함수형 프로그래밍)을 연습해야겠다 생각하였고,
그러한 측면에서, matchKey와 isMatched는 사용인자와 역할이 비슷한 점에서 하나로 묶어도 되지 않을까 나름대로 추정해보았다.
반응형'Algorithm' 카테고리의 다른 글
[CodeKata] 프로그래머스 : 5.4(화), 가장 먼 노드 (0) 2021.05.04 [CodeKata] 프로그래머스 : 5.3(월), 네트워크 (0) 2021.05.03 [CodeKata] 프로그래머스 : 4.30(금), 2 x n 타일링 & N으로 표현 (0) 2021.04.30 [CodeKata] 프로그래머스 : 4.28(수), 추석 트래픽 (0) 2021.04.28 [CodeKata] 프로그래머스 : 4.21(수), n진수 게임 (0) 2021.04.21