알고리즘/문제

프로그래머스 Level2 프린터

무삿 2023. 4. 14. 02:26

프린터

출처: 프로그래머스

https://school.programmers.co.kr/learn/courses/30/lessons/42587

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

문제 풀이

문제에 대해 간단히 설명드리면 

인쇄 요청을 하는 배열이 들어옵니다.

이제 인쇄 요청대로 맨 앞의 문서부터 프린터하려고 하는데 각 문서마다 고유한 우선순위가 있습니다.

맨 처음 순서에 인쇄하려는 문서를 확인해보니 이 문서보다 우선순위가 높은 문서가 목록에 있으면 

이 손에 들고있는 문서를 요청 목록 맨 뒤에 다시 넣습니다. 그리고 다시 맨 앞에서 문서 하나를 꺼내고 우선순위를 확인하고 제일 우선순위가 높은 문서이면 처리하고 아니면 다시 맨 뒤로 넣고 반복하는 겁니다.

근데 여기서 한 사람이 처음 요청 목록에 3번째에 자기 문서가 있다고 가정하고 요청 목록 3번째에 있는 자신의 문서가 언제 처리가 되는지 알고싶다는 겁니다. 

 

처음 생각한 건 

아래와 같은 코드입니다.

선행으로 아이템을 먼저 지정해두고 while 반복을 통해 ArrayDeque에서 확인하는 솔루션입니다.

count로 만약에 맨 앞의 문서보다 value 값이 큰 item이 있으면 앞에서 빼고 뒤로 넣고 item이 내가 찾는 findingitem이면 스탑하고 그때의 answer를 반환합니다.

class Solution {
    fun solution(priorities: IntArray, location: Int): Int {
        var answer = 0
        val queue = ArrayDeque<Item>()
        val findingItem = Item(value = priorities[location], location = location)
        priorities.forEachIndexed { index, i ->
            val data = Item(
                value = i,
                location = index
            )
            queue.add(data)
        }
        while(queue.isNotEmpty()) {
            val first = queue.first()
            if(queue.count { it.value > first.value } != 0 ) {
                queue.addLast(first)
                queue.removeFirst()
            } else {
                if(first == findingItem) break
                else {
                    queue.removeFirst()
                    answer++
                }
            }
        }

        return answer+1
    }
}

data class Item(
    val value: Int,
    val location: Int
)

 

효율은 아래와 같습니다.

좀 더 생각해보니 이것보다 더 빠르게 풀 수 있을 것 같다는 생각이 들었습니다.

먼저, count를 세는건 비효율적입니다.

find 함수를 통해 찾아볼까도 했지만 만약 존재하지 않는다면 그때도 결국 n을 한 번 다 돌아야하기 때문에 내키지 않았습니다.

 

곰곰히 생각해보니 여기서 PriorityQueue를 사용해서 해보자! 라는 아이디어가 떠올랐고 시도해보았습니다.

 

이게 훨씬 재밌습니다.

 

PrirorityQueue를 이용한 최대힙 과정의 풀이

 

처음에는 Document data class를 만들어서 deq이랑 priorityQ에 둘 다 넣어줬습니다.

import java.util.*

class Solution {
    fun solution(priorities: IntArray, location: Int): Int {
        var answer = 1
        val deq = ArrayDeque<Document>()
        val priorityQ = PriorityQueue<Document>(compareBy{-it.priority})
        priorities.forEachIndexed { i, p ->
            val doc = Document(i,p)
            deq.addLast(doc)
            priorityQ.add(doc)
        }
        while(deq.isNotEmpty()) {
            val currentDoc = deq.removeFirst()
            if(currentDoc.priority < priorityQ.peek().priority) {
                deq.addLast(currentDoc)
            } else {
                priorityQ.poll()
                if(currentDoc.index == location) break
                else answer++
            }
        }
        return answer
    }
}

 data class Document(
     val index : Int,
     val priority: Int
 )

 

위와 같이 풀었을때 효율성은 아래와 같습니다. 맨 처음보다는 좋아졌습니다.

 

한 번더 코드를 곰곰히 살펴보니, PriorityQueue에 굳이 Document를 넣지 않아도 되겠다 생각했습니다.

index를 사용하지 않는데 필요할까라는 생각에 코드를 수정하였습니다.

아래와 같습니다.

변경한 부분은 prirorityQ 변수 타입을 Int로 변경하고 priorities.forEach안에 -p를 넣어서 큰 수가 위로 가게 해줍니다.

그리고 밑에 while문 안에 if를 보시면 -priorityQ.peek()으로 다시 -를 사용하여 양수로 바꿔줍니다.

import java.util.*

class Solution {
    fun solution(priorities: IntArray, location: Int): Int {
        var answer = 1
        val deq = ArrayDeque<Document>()
        val priorityQ = PriorityQueue<Int>()
        priorities.forEachIndexed { i, p ->
            val doc = Document(i,p)
            deq.addLast(doc)
            priorityQ.add(-p)
        }
        while(deq.isNotEmpty()) {
            val currentDoc = deq.removeFirst()
            if(currentDoc.priority < -priorityQ.peek()) {
                deq.addLast(currentDoc)
            } else {
                priorityQ.poll()
                if(currentDoc.index == location) break
                else answer++
            }
        }
        return answer
    }
}

 data class Document(
     val index : Int,
     val priority: Int
 )

 

효율을 보시면 조금이지만 확실히 줄어든게 보입니다. 

PrirorityQ에서 Document 객체를 사용하지 않으므로 시간이 줄어들었습니다.

 

하나 더 추가로 해본건 Pair를 쓰면 어떨까 였는데

 

큰 차이가 없습니다. 

Pair도 data class여서 크게 다르지 않아 그런것 같습니다.

의미있는 변경은 아니었다!

1번에서의 결론은! 우선순위 큐를 이용해 현재 순위가 낮으면 뒤에 넣고 확인하는게 더 빠르다!

 

문제를 풀면서 여러가지 궁금한 것들이 있는데 그 중 하나 블로그 링크를 남깁니다.

정렬 방식은 어떨까 하고 시도해봤는데 갑자기 kotlin에서의 정렬방식이 궁금해 샛길로 빠져서 찾아보다가 재밌는 블로그가 있어서 한 번 읽어보시라고 남기고 갑니다.

정말 가도가도 끝이 없습니다. Prirority Queue도 더 파봐야겠습니다. 재밌네요.

https://ongveloper.tistory.com/385

 

[Kotlin/Java] Kotlin/java의 sort 동작 방식 (2022.10.18 수정)

환경 : Kotlin Version = 1.5.20, Java version = 14.0.2 JVM, Android Studio Kotlin/Java의 sort 동작 방식 알아보기 0. 결론 글이 길어져서 결론부터 말하자면, 코틀린과 자바에서 Arrays.sort는 Dual-Pivot QuickSort Collections.so

ongveloper.tistory.com

 

아무튼 이렇게 문제를 풀어봤습니다. 읽어주셔서 감사합니다!~ 안녕~~~~