본문 바로가기
코딩테스트

[프로그래머스] 택배 상자 꺼내기

by liz_devel 2025. 11. 11.

🗒 문제


📝 나의 문제풀이

class Solution {
    fun solution(n: Int, w: Int, num: Int): Int {
        // n개를 w만큼 끊어서 배열을 만든다 (입출력 예 1로 예시)
        // (1,2,3,4,5,6)
        // (12,11,10,9,8,7)
        // (13,14,15,16,17,18)
        // (22,21,20,19)
        val tempResult = (1..n)
            .chunked(w)
            .mapIndexed { index, value ->
                if (index % 2 == 1) value.reversed() else value
            }

        // 마지막 줄이 덜 찼으면 0으로 채워 길이 맞추기
        val result = tempResult.toMutableList()
        val lastIndex = result.lastIndex
        val lastChunk = result.last().toMutableList()
        val missing = w - lastChunk.size

        if (missing > 0) {
            if (lastIndex % 2 == 1) {
                // 홀수층 → 앞쪽(왼쪽)에 0 채우기
                repeat(missing) { lastChunk.add(0, 0) }
            } else {
                // 짝수층 → 뒤쪽(오른쪽)에 0 채우기
                repeat(missing) { lastChunk.add(0) }
            }
            result[result.lastIndex] = lastChunk
        }

        // 몇번째 index에 포함되어있는지
        val foundIndex = result.indexOfFirst { num in it }
        // (12,11,10,9,8,7)
        val foundChunk = result[foundIndex]
        val isReverse = foundIndex % 2 == 1
        val isLastReverse = result.lastIndex % 2 == 1

        // num이 속한 위치
        val idx = foundChunk.indexOf(num)

        // 같은 방향이면 동일 인덱스, 다르면 반대 인덱스
        val checkIndex =
            if (isReverse == isLastReverse) idx - (foundChunk.size - lastChunk.size)
            else idx

        val isExist = lastChunk.getOrNull(checkIndex) != null

        return if (isExist && result.last()[idx] != 0) {
            result.size - foundIndex
        } else {
            result.size - 1 - foundIndex
        }
    }
}

📝 다른 사람의 문제 풀이

다른 사람 문제 풀이에서 가독성이 좋은 코드를 발견하지 못하여 GPT 풀이로 대체하였습니다

class Solution {
    fun solution(n: Int, w: Int, num: Int): Int {
        // 층 구성: 한 층에 w개씩 쌓되, 홀수층은 역방향(오른→왼)
        val rows = (1..n)
            .chunked(w)
            .mapIndexed { i, list -> if (i % 2 == 1) list.reversed() else list }

        // 마지막 층이 덜 찼으면 0으로 채워서 모양 맞추기
        val paddedRows = padLastRow(rows, w)

        // num이 들어 있는 층과 위치 찾기
        val rowIndex = paddedRows.indexOfFirst { num in it }
        val columnIndex = paddedRows[rowIndex].indexOf(num)

        // 마지막 층 방향 확인 (짝수층이면 정방향)
        val lastRow = paddedRows.last()
        val sameDirection = (rowIndex % 2 == paddedRows.lastIndex % 2)

        // 마지막 층에도 같은 위치(또는 대칭 위치)에 상자가 있는지 확인
        val checkIndex =
            if (sameDirection) columnIndex - (paddedRows[rowIndex].size - lastRow.size)
            else columnIndex
        val hasBoxAbove = lastRow.getOrNull(checkIndex)?.takeIf { it != 0 } != null

        // 꺼내야 하는 상자 개수 계산
        return if (hasBoxAbove) paddedRows.size - rowIndex else paddedRows.size - 1 - rowIndex
    }

    // 마지막 층이 덜 찼으면 빈 공간(0)으로 채워줌
    private fun padLastRow(rows: List<List<Int>>, width: Int): List<List<Int>> {
        val result = rows.toMutableList()
        val last = result.last().toMutableList()
        val missing = width - last.size
        if (missing > 0) {
            if (result.lastIndex % 2 == 1) repeat(missing) { last.add(0, 0) } // ← 홀수층: 앞에 0 채우기
            else repeat(missing) { last.add(0) }                             // → 짝수층: 뒤에 0 채우기
            result[result.lastIndex] = last
        }
        return result
    }
}

 


🖊 문제 풀이 시 알면 좋을 것

chunked()

리스트를 일정한 크기로 나누는 함수.
택배 상자를 층 단위로 자를 때 사용.

 
val result = (1..22).chunked(6)
// [[1,2,3,4,5,6], [7,8,9,10,11,12], [13,14,15,16,17,18], [19,20,21,22]]

 

 

 

mapIndexed()

리스트의 인덱스와 값을 함께 사용해 변환할 때 사용.
층 번호(index)에 따라 방향(정방향/역방향) 을 다르게 지정 가능.

val rows = (1..22).chunked(6).mapIndexed { i, row ->
    if (i % 2 == 1) row.reversed() else row
}
  • 짝수층(→): [1,2,3,4,5,6]
  • 홀수층(←): [12,11,10,9,8,7]

 

toMutableList()

불변 리스트를 가변 리스트로 변경.
리스트의 마지막 층을 수정(0으로 채우기 등)할 때 필요.

val lastRow = result.last().toMutableList()
lastRow.add(0)  // 가능

 

 

 

repeat()

특정 횟수만큼 코드를 반복 실행.
빈 칸을 0으로 채울 때 자주 사용.

 
val missing = w - lastRow.size
repeat(missing) { lastRow.add(0) }

 

 

 

getOrNull()

리스트 인덱스를 안전하게 접근할 때 사용.
인덱스가 범위를 벗어나면 null 반환 → 예외 방지.

val value = lastRow.getOrNull(index)
if (value != null) println(value)

 

 

 

takeIf()

조건이 true일 때만 해당 객체를 반환, 아니면 null.
null-safe하게 조건 검사를 간결하게 쓸 수 있음.

 
val hasBox = lastRow.getOrNull(checkIndex)?.takeIf { it != 0 } != null

→ checkIndex 위치에 실제 상자(0이 아님)가 있으면 true


% (mod 연산자)

홀수/짝수층 구분에 사용.
층 번호(index)가 홀수면 역방향, 짝수면 정방향.

val isReverse = index % 2 == 1

 

 

indexOfFirst() / indexOf()

리스트에서 특정 값의 위치를 찾는 함수.
상자가 어느 층, 어느 열에 있는지 찾을 때 사용.

val rowIndex = rows.indexOfFirst { num in it }   // 층 번호
val colIndex = rows[rowIndex].indexOf(num)       // 열 번호

 

 

조건식 기반 인덱스 보정

층 방향이 다를 경우, 인덱스 계산을 대칭시켜 맞춰줌.

val checkIndex =
    if (sameDirection) idx - (foundRow.size - lastRow.size)
    else idx

 

 

 

조건에 따라 결과 반환

꺼내야 하는 상자의 개수를 상황에 맞게 계산.

 
return if (hasBoxAbove) rows.size - rowIndex
       else rows.size - 1 - rowIndex

 

 

 

패딩(padding) 처리 로직

마지막 층이 w보다 작을 때 모양을 맞추기 위해 0으로 채움.
짝수층(→)은 뒤에, 홀수층(←)은 앞에 채움.

if (missing > 0) {
    if (rows.lastIndex % 2 == 1) repeat(missing) { lastRow.add(0, 0) }
    else repeat(missing) { lastRow.add(0) }
}

 

 

함수 분리

유지보수를 쉽게 하려면 역할별로 함수 분리.
예: padLastRow() 처럼 “마지막 층 정렬” 기능만 따로 관리.

private fun padLastRow(rows: List<List<Int>>, width: Int): List<List<Int>> { ... }

 

 

 

 

 

개념 키워드 역할
층 나누기 chunked() n개의 상자를 w개씩 나눔
층 방향 설정 mapIndexed() 홀/짝 방향 제어
안전한 접근 getOrNull(), takeIf() null-safe 처리
보정 repeat(), % 부족한 칸 채우기 / 홀짝 판단
탐색 indexOfFirst(), indexOf() 상자의 위치 찾기
유지보수 함수 분리 구조 명확하게 관리

 

 

이 문제의 포인트는 2차원 행렬을 시뮬레이션하되, 방향 전환과 마지막 층 보정을 안전하게 처리하는 것입니다.

chunked(), mapIndexed(), getOrNull(), takeIf()
네 가지 함수만 정확히 이해하면 어떤 형태의 창고 문제도 풀 수 있을 것입니다.

반응형