시리즈: 알고리즘 (32개 글)
- 자바로 알고리즘 풀기
- [Java] 백준 10431 - 줄 세우기
- [Java & Node.js] 백준 9935 - 문자열 폭발
- [Node.js] 백준 22866 - 탑 보기
- [Java] 백준 4485 - 녹색 옷 입은 애가 젤다지?
- [Node.js] 백준 1283 - 단축키 지정
- [Java & Node.js] 백준 14469 - 소가 길을 건너간 이유 3
- [Java] 백준 8979 - 올림픽
- [Node.js] 백준 22866 - 탑 보기
- [Java] 백준 7568 - 덩치
- [Java] 백준 1244 - 스위치 켜고 끄기
- [Node.js] 백준 19941 - 햄버거 분배
- [Node.js] 백준 1254 - 팰린드롬 만들기
- [Java] 백준 2589 - 보물섬
- [Java] 백준 2309 - 일곱 난쟁이
- [Java] 백준 1789 - 수들의 합
- [Java] 백준 1987 - 알파벳
- [Node.js] [Rust] 백준 24460 - 특별상이라도 받고 싶어
- [Node.js] 백준 7576 - 탑 보기
- [Rust] 백준 1047 - 울타리 (N^4 브루트포스 + 그리디)
- [Node.js] 백준 9657 - 돌게임3
- [Rust] 백준 4963 - 섬의 개수
- [Node.js] 프로그래머스 - 모의고사 / 실패율
- [Rust] 백준 2667 - 단지번호붙이기
- [Node.js] 프로그래머스 - 방문 길이
- [Node.js] 프로그래머스 - 괄호 회전하기
- [Rust] 백준 1002 - 터렛
- [Node.js] 프로그래머스 - 크레인 인형뽑기 게임
- [Node.js] 프로그래머스 - 기능개발
- [Rust] 백준 13717 - 포켓몬 GO
- [JavaScript] 백준 1092 - 배
- [Rust] 백준 25329 - 학생별 통화 요금 계산
문제
문제 풀이
가장 직관적입 방법은 moves를 순회하면서 board 배열에 깊이 0 부터 인형을 찾는 것.
다만 이런 과정은 $O(N)$의 과정이 인형을 뽑을 때 마다 발생한다는 생각이 들었다.
깊이를 관리하는 것도 귀찮게 느껴지기는 했고.
따라서 해당 탐색 시간을 미리 탐색을 해 둔 상태로 진행하기.
전처리
접근시간을 $O(1)$로 줄이기 위함
의사코드 : 각 줄 별로 별도의 배열을 만들어 쉽게 pop() 하자.
크레인 게임에서 특정 열의 가장 위 인형을 찾는 것은 스택의 pop()과 동일하다.
따라서 board 배열을 열 기준으로 재구성하여 인형을 밑에서 위로 쌓아올린 배열을 사용할 것이다. (boardByLineIdx)
시간 복잡도
\[O(N \times M)\]보드 모든 칸을 한 번 순회하기 때문.
보드 크기가 $30 \times 30$ 인 경우에도 900번의 연산만 필요하다.
메인 로직
boardByLineIdx를 준비했으므로, moves 배열을 순회하며 인형을 뽑는 과정은 매우 빨라진다.
1. 인형 뽑기와 예외 처리
의사코드 : 해당 줄에 아무것도 없다면 넘겨버린다.
moves의 값은 1 부터 시작하므로 배열 인덱스 lineIdx = move - 1 로 변환하여 접근한다.
// ... 기타 코드
for (const move of moves) {
const lineIdx = move - 1;
if (boardByLineIdx[lineIdx].length === 0) {
continue;
}
// O(1) 뽑기
const curTarget = boardByLineIdx[lineIdx].pop();
}
2. 바구니 (Stack) 로직
의사코드 : 스택에 쌓기 전 판단하여 연쇄반응을 미리 배제하고, 성립되면 answer += 2
바구니 역시 stack으로 작동한다. 새로 뽑은 인형을 바구니에 넣기 전, 바구니 맨 위 인형과 비교한다
if (basket.length > 0 && basket[basket.length - 1] === curTarget) {
basket.pop(); // O(1)
answer += 2;
} else {
basket.push(curTarget); // O(1)
}
이 길이는 moves 배열의 길이(L) 만큼 반복되며, 내부 연산은 모두 1 이므로, 시간복잡도는 $O(L)$
최종 시간 복잡도와 공간 복잡도
\[O(N^2 (보드의 크기) + L (움직임))\]공간 복잡도 고민 (feat 함수)
- 새로운 배열을 만든 이유 : 시간 복잡도 이득이 있을 거라고 생각함. 또한 실제 코드를 쓰는게 간편해짐
단 이런 배열로 인해 $O(N \times M)$ 의 공간이 추가적으로 필요하다
정답 코드
function solution(board, moves) {
let answer = 0;
const boardLen = board.length;
const lineLen = board[0].length;
const boardByLineIdx = [];
for (let line = 0; line < lineLen; line++) {
const tmp = [];
for (let depth = 0; depth < boardLen; depth++) {
const curDoll = board[depth][line];
if (curDoll !== 0) {
tmp.push(curDoll);
}
}
boardByLineIdx.push(tmp.reverse());
}
const basket = [];
for (const move of moves) {
const lineIdx = move - 1;
// 인형이 없는 경우 아무 일 없음.
if (boardByLineIdx[lineIdx].length <= 0) {
continue;
}
const curTarget = boardByLineIdx[lineIdx].pop();
if (basket.length > 0 && basket[basket.length - 1] === curTarget) {
basket.pop();
answer += 2;
} else {
basket.push(curTarget);
}
}
return answer;
}
마무리 및 여담
문제를 풀며 공간복잡도와 시간 복잡도에 대해 한 번 다시 생각해보게 되었다.
또 문제를 풀고 이렇게 회고하면서 아 이렇게 하지 말걸 혹은 이건 이래서 마음에 들어! 라는 부분이 있었다.
변수명 진짜 어려움
남의 풀이도 보면서 시간/공간 복잡도를 생각할 수 있다면 더욱 좋은 코드를 작성할 수 있게 되지 않을까?
화이팅화이팅
-뱅-
