

난이도 : Level 1
문제 출처
아래에서 위로, 올라가면서 'ㄹ' 리을 자로 번호가 올라가는데
특정 숫자값이 주어졌을 때 해당 숫자값 위로 몇 개의 숫자가 더 있는지 파악하는 문제이다.
즉, 꺼내려는 상자 num이 주어졌을 때, 그 상자를 꺼내기 위해 위에 쌓여 있는 상자까지 포함해서 총 몇 개의 상자를 꺼내야 하는지 구하면 된다.
처음에는 2차원 배열을 'ㄹ'자로 만들기 번거로울 거라 생각했다.
어차피 맨 위 row 말고 아래 row는 다 차 있을 테니까,
row가 맨 위에 있는지만 판별해서 홀수, 짝수를 판별해 맨 위만 뒤집어서 풀고자 했다.
그런데 JS에 reverse() 메서드가 있는 게 떠올랐고,
생각보다 그냥 전체 2차원 배열을 'ㄹ'자로 만들어서 푸는 게 더 수월할 것 같았다.
그래서 가장 먼저 생각난 정직한 방법으로 풀고자 했다.
전체 2차원 배열을 만들고, 특정 num 값의 위치 좌표를 찾아서
그 위에 좌표가 총 몇 개 있는지 파악하는 방식이다.
// 상자 개수 n, 가로 한 줄 상자 개수 w, 꺼내려는 상자의 번호 num
// 꺼내야 하는 상자의 총 개수 return
// 어떻게 풀까?
// 1. 완전탐색 생각
// 상자 개수 확인 -> 상자개수가 100 이하네? 포문 두세번 돌려도 가능하니 완탐가능한 점 확인
function solution(n, w, num) {
// board 만들기 - 1 ~ 22 w 구간단위로 슬라이싱, 홀짝 판별해 뒤집기
const board = []
for (let i = 1; i<n+1; i++){
board.push(i)
}
// [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]
const sliced_board = []
for (let i = 0; i<n; i+=w){ // 0, 6, 12, 18,
sliced_board.push(board.slice(i,i+w))
}
// [[1,2,3,4,5,6],[7,8,9,10,11,12],[13,14,15,16,17,18],[19,20,21,22]]
// 이게 어떻게 뒤에 23,24가 없는데 에러가 안나지? -> 슬라이싱은 뒤에 숫자 있어도 있는곳까지 잘라서 반환함 (JS,Python 동일)
// 맨 뒤에꺼 빈칸 Null 추가
if (sliced_board[sliced_board.length-1].length !== sliced_board[0].length){
while (sliced_board[sliced_board.length-1].length != sliced_board[0].length) {
sliced_board[sliced_board.length-1].push(null)
}
}
// [[1,2,3,4,5,6],[7,8,9,10,11,12],[13,14,15,16,17,18],[19,20,21,22,null,null]]
const reversed_board = sliced_board.map((row,i) => {
if ( (i+1) % 2 == 1){
return row
} else {
return row.reverse()
}
})
const boxes = reversed_board.reverse()
// [
// [null,null,22,21,20,19],
// [13 , 14 , 15,16,17,18],
// [12 , 11 , 10,9 ,8 ,7],
// [1 , 2 , 3,4 ,5 ,6]
// ]
let result = 1
for (let i = 0; i < boxes.length; i++){
for (let j = 0; j < boxes[0].length; j++){
if (boxes[i][j] === num){
let ni = i - 1
while (true) {
// 범위내에 존재, null이 아니라면
if (ni >= 0 && boxes[ni][j] !== null) {
result += 1
ni -= 1
} else {
break
}
}
}
}
}
return result
}
JS 풀이를 Python으로 그대로 옮기면 다음과 같다.
JS에서는 빈칸을 null로 표현했지만, Python에서는 None을 사용한다.
또 JS의 reverse() 대신 Python에서는 [::-1] 슬라이싱을 사용해 배열을 뒤집을 수 있다.
def solution(n, w, num):
# board 만들기 - 1 ~ n까지 상자 번호 생성
board = []
for i in range(1, n + 1):
board.append(i)
# w개씩 잘라서 2차원 배열 만들기
sliced_board = []
for i in range(0, n, w):
sliced_board.append(board[i:i + w])
# 맨 뒤에 있는 row에 빈칸 None 추가
if len(sliced_board[-1]) != len(sliced_board[0]):
while len(sliced_board[-1]) != len(sliced_board[0]):
sliced_board[-1].append(None)
# 홀수 번째 줄은 그대로, 짝수 번째 줄은 뒤집기
reversed_board = []
for i, row in enumerate(sliced_board):
if (i + 1) % 2 == 1:
reversed_board.append(row)
else:
reversed_board.append(row[::-1])
# 위에서 내려다보는 형태로 뒤집기
boxes = reversed_board[::-1]
result = 1
for i in range(len(boxes)):
for j in range(len(boxes[0])):
if boxes[i][j] == num:
ni = i - 1
# 범위 내에 존재하고, None이 아니라면
while ni >= 0 and boxes[ni][j] is not None:
result += 1
ni -= 1
return result
JS에서 다음과 같이 작성했을 때,
board.slice(i, i + w)
i + w가 배열 길이를 넘어가더라도 에러가 나지 않는다.
예를 들어 배열에 23, 24가 없어도 slice(18, 24)를 하면
존재하는 값까지만 잘라서 반환한다.
그래서 마지막 줄이 [19, 20, 21, 22]처럼 짧게 만들어질 수 있다.
이후 마지막 줄의 길이를 첫 번째 줄과 맞추기 위해 null을 추가했다.
처음에는 위쪽 row의 인덱스만 확인해서 카운트하려고 했다.
하지만 마지막 줄에 빈칸 null이 들어갈 수 있기 때문에
단순히 인덱스가 존재하는지만 보면 안 된다.
반드시 해당 위치가 null이 아닌지 확인해야 한다.
boxes[ni][j] !== null
Python에서는 다음처럼 확인한다.
boxes[ni][j] is not None
JS의 reverse()는 원본 배열을 직접 뒤집는다.
이번 풀이에서는 원본을 다시 사용할 일이 없어서 괜찮았지만,
다른 문제에서는 원본 배열이 바뀌는 것 때문에 실수할 수 있다.
원본을 유지하고 싶다면 다음처럼 복사 후 뒤집는 방식도 가능하다.
[...row].reverse()
Python에서는 row[::-1]을 사용하면 뒤집힌 새 리스트를 만들 수 있다.
지금 풀이처럼 2차원 배열을 직접 만들어도 통과할 수 있다.
제한사항에서 n이 크지 않기 때문에 완전탐색으로 충분하다.
다만 더 잘 풀려면 전체 보드를 만들지 않고,
num이 위치한 row와 col만 계산해서 풀 수 있다.
핵심은 다음과 같다.
num이 아래에서부터 몇 번째 줄에 있는지 구한다.'ㄹ'자 배치이므로 row의 홀짝에 따라 실제 col 위치를 계산한다.function solution(n, w, num) {
// num이 몇 번째 줄에 있는지
// 아래에서부터 0번째 줄, 1번째 줄, 2번째 줄...
const row = Math.floor((num - 1) / w);
// 해당 줄에서 몇 번째 위치인지
const pos = (num - 1) % w;
// ㄹ자 배치이므로 줄 방향에 따라 실제 열이 달라짐
const col = row % 2 === 0 ? pos : w - 1 - pos;
let count = 0;
// num이 있는 줄부터 위쪽 줄까지 확인
for (let r = row; r * w < n; r++) {
let box;
if (r % 2 === 0) {
// 왼쪽 -> 오른쪽 줄
box = r * w + col + 1;
} else {
// 오른쪽 -> 왼쪽 줄
box = r * w + (w - col);
}
if (box <= n) {
count++;
}
}
return count;
}
def solution(n, w, num):
# num이 몇 번째 줄에 있는지
# 아래에서부터 0번째 줄, 1번째 줄, 2번째 줄...
row = (num - 1) // w
# 해당 줄에서 몇 번째 위치인지
pos = (num - 1) % w
# ㄹ자 배치이므로 줄 방향에 따라 실제 열이 달라짐
if row % 2 == 0:
col = pos
else:
col = w - 1 - pos
count = 0
# num이 있는 줄부터 위쪽 줄까지 확인
r = row
while r * w < n:
if r % 2 == 0:
# 왼쪽 -> 오른쪽 줄
box = r * w + col + 1
else:
# 오른쪽 -> 왼쪽 줄
box = r * w + (w - col)
if box <= n:
count += 1
r += 1
return count
예를 들어 n = 22, w = 6, num = 13이라고 해보자.
상자는 아래처럼 쌓인다.
[
[null, null, 22, 21, 20, 19],
[13, 14, 15, 16, 17, 18],
[12, 11, 10, 9, 8, 7 ],
[1, 2, 3, 4, 5, 6 ]
]
13은 아래에서부터 2번째 줄에 있다.
Math.floor((13 - 1) / 6) // 2
그리고 해당 줄에서의 위치는 다음과 같다.
(13 - 1) % 6 // 0
row가 짝수 번째 줄이면 왼쪽에서 오른쪽으로 번호가 증가하므로
실제 col은 그대로 0이다.
그래서 13 위 같은 열을 확인하면 null밖에 없기 때문에
꺼내야 하는 상자의 개수는 13 자기 자신만 포함해서 1개가 된다.
이번 문제를 풀면서 JS와 Python에서 대응되는 문법도 함께 정리할 수 있었다.
| 개념 | JavaScript | Python |
|---|---|---|
| 빈 값 | null | None |
| 배열 뒤집기 | arr.reverse() | arr[::-1] |
| 몫 구하기 | Math.floor(a / b) | a // b |
| 나머지 구하기 | a % b | a % b |
| 배열 길이 | arr.length | len(arr) |
| 반복문 인덱스 | for (let i = 0; i < n; i++) | for i in range(n) |
이번 문제는 처음 봤을 때 'ㄹ'자 배치 때문에 복잡해 보였지만,
제한사항을 확인해보면 2차원 배열을 직접 만들어서 풀어도 충분한 문제였다.
내가 처음 선택한 풀이는 다음과 같다.
1부터 n까지 상자 번호를 배열로 만든다.w개씩 잘라 2차원 배열을 만든다.null을 채운다.num의 위치를 찾고, 그 위에 있는 상자를 센다.이 방식은 문제 상황을 그대로 코드로 옮기는 시뮬레이션 풀이이다.
더 나은 방법으로는 전체 보드를 만들지 않고,
num의 row와 col만 계산해서 같은 열 위쪽에 상자가 몇 개 있는지 확인할 수 있다.
이번 문제에서 얻어갈 수 있는 핵심은 다음과 같다.
'ㄹ'자 구조는 row의 홀짝으로 방향을 판단할 수 있음