FrontEnd
Javascript
Diary
ML
CS
Django
Algorithm
AWS
Co-Work
HTML
CSS
Python
React
ReactNative

#60 알고리즘 연습 - 프린터(Python)

프린터

문제 설명 일반적인 프린터는 인쇄 요청이 들어온 순서대로 인쇄합니다. 그렇기 때문에 중요한 문서가 나중에 인쇄될 수 있습니다. 이런 문제를 보완하기 위해 중요도가 높은 문서를 먼저 인쇄하는 프린터를 개발했습니다. 이 새롭게 개발한 프린터는 아래와 같은 방식으로 인쇄 작업을 수행합니다.

  1. 인쇄 대기목록의 가장 앞에 있는 문서(J)를 대기목록에서 꺼냅니다.
  2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 한 개라도 존재하면 J를 대기목록의 가장 마지막에 넣습니다.
  3. 그렇지 않으면 J를 인쇄합니다.

예를 들어, 4개의 문서(A, B, C, D)가 순서대로 인쇄 대기목록에 있고 중요도가 2 1 3 2 라면 C D A B 순으로 인쇄하게 됩니다.

내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 알고 싶습니다. 위의 예에서 C는 1번째로, A는 3번째로 인쇄됩니다.

현재 대기목록에 있는 문서의 중요도가 순서대로 담긴 배열 priorities와 내가 인쇄를 요청한 문서가 현재 대기목록의 어떤 위치에 있는지를 알려주는 location이 매개변수로 주어질 때, 내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 현재 대기목록에는 1개 이상 100개 이하의 문서가 있습니다.
  • 인쇄 작업의 중요도는 1~9로 표현하며 숫자가 클수록 중요하다는 뜻입니다.
  • location은 0 이상 (현재 대기목록에 있는 작업 수 - 1) 이하의 값을 가지며 대기목록의 가장 앞에 있으면 0, 두 번째에 있으면 1로 표현합니다.

입출력 예

priorities location return
[2, 1, 3, 2] 2 1
[1, 1, 9, 1, 1, 1] 0 5

입출력 예 설명 예제 #1

문제에 나온 예와 같습니다.

예제 #2

6개의 문서(A, B, C, D, E, F)가 인쇄 대기목록에 있고 중요도가 1 1 9 1 1 1 이므로 C D E F A B 순으로 인쇄합니다.

문제풀이 IDEA

예전에 여러 방면으로 고민을 하다가 막혔었던 문제인데, 이번에도 스스로 제한사항(예를 들면 max를 한번만 써야한다던지)을 많이 걸어버려서인지 잘 풀리지 않았다. 그래서 일단 내가 사고하는대로 code를 짜보자고 생각했고, time complexity는 조금 안 좋더라도 논리적인 구성은 나름 효율적이게 코드를 짜는데 집중을 했다.

일단 해당 문제에서 짚어야할 포인트는 첫번째 인덱스를 pop하는데 만약 뒤에 max값이 있으면 max값부터 pop을 해주고 앞에 위치한 애들은 list의 마지막에 append를 해준다는것이다.

그리고 또 한가지 location으로 주어진 값과 같은 값이 여러개 있을 경우 주어진 location값을 표시해서 빼낼 수 있도록 만들어 주는 것이다.

그래서 말그대로 location값을 다른 곳에 임시 보관해주고, location값은 0으로 치환을해서 max값에 영향을 안 받고, 식별 가능하게 처리를 해주었다.

그 후로는 말그대로 location값이 반환될수 있는 조건을 여러개로 분기하여 if문을 작성해주고, 그간 앞서 출력되는 값을 cnt해주고 list를 잘라서 붙여주기 위해 while과 for를 이용한 반복문을 구성해주었다.

내 풀이 🌱

def solution(p, lo):
    flag = p[lo] #location값을 임시보관하는 flag이다.
    cnt = 1 #만약 loc값이 max이면 처음 return값은 1이되야하니까 초깃값은 1이다.

    if p.count(flag) != 1: #만약 flag와 같은 값들이 1개가 아니라 여러개라면 location값을
        p[lo] = 0          #구분하기위해서 0으로 만들어준다.

    while True:            #특별한 조건이 없으면 계속 반복하게한다(return으로 끝내주면 되니까 True로 설정)
        max_v = max(p)     #주어진 list에서 max값을 찾아서 사용해주고

        if max_v == flag:  #만약 loc의 값이랑 max값이랑 같아지는 순간은 더 이상 큰 값이 list안에 없다는 것,
            if p.count(flag) == 1 and not p.count(0): #이 조건은 flag와 같은 값이 1개만 존재하고, 0이 없다는 조건인데
                return cnt        #즉 loc와 같은 값이 처음부터 1개만 존재했을 경우를 말한다.(지금생각하니 p.count는 안 붙여도 됐을듯)
            for i in p:           #만약 list안에 loc와 같은 값들이 여러개 있으면 list를 돌면서 0이라고 체크된 loc에 도달할때까지
                if i == 0:        #flag와 같은 크기의 값들은 계속 pop되는 식이라서 cnt가 올라가고 0에 도달하면 우리의 loc이 pop되면 되니까 return으로 끝내준다.
                    return cnt
                if i == flag:
                    cnt += 1

        for i in range(len(p)): #얘는 list안에 loc(flag) 보다 큰 값이 있을때를 말하는데

            if p[i] == max_v:   #만약 지금 순회하고 있는 value가 최댓값이랑 같으면
                p = p[i+1:]     #이 index이후부터 다시 p에다가 할당을 해주고
                cnt += 1        #pop이 되었으니 cnt +=1 을 해준다.
                break
            else:
                p.append(p[i])  #만약 최댓값이 아니라면 뒤에 쭈루루미 붙여준다.

이 문제부터는 TESTCASE를 잘 캐치하는 것도 중요했는데 p= [9, 9, 9, 9, 9], loc = 3, return = 4 이 TESTCASE가 나한테는 아주 중요한 실마리를 준 CASE이다.

위의 주석과 코드를 보면 문제가 시키는 것처럼 최댓값을 만날때까지 뒤에다가 붙여주고, 최댓값을 만나면 pop 그리고 나의 loc에 있던 값이 max이자 맨앞으로 오면 pop을 해주면서 몇번째 pop인지 알려주는 코드이다.

print()만으로 찍어보는데는 한계가 있어 vscode의 debug기능을 사용하니까 가시적으로 code가 어떻게 돌아가고 변수가 어떻게 변하는지 볼수 있어서 아주아주 유용했다.

다른 풀이 🌳

def solution(priorities, location):
    queue =  [(i,p) for i,p in enumerate(priorities)]
    answer = 0
    while True:
        cur = queue.pop(0)
        if any(cur[1] < q[1] for q in queue):
            queue.append(cur)
        else:
            answer += 1
            if cur[0] == location:
                return answer
  • 위의 코드에서 눈여겨 볼 부분은 (i,p)를 이용해서 같은 값이라 할지라도 location을 tuple값으로 같이 묶어주어서 판별을 해준다는 것이다.
  • 그리고 any라는 내장함수를 이용해 for문을 돌면서 만약 1개라도 cur값보다 큰게 있으면 if 를 False로 만들어주어서 제어를 해주게된다.

다른 풀이2 🌳

def solution(priorities, location):
    answer = 0
    search, c = sorted(priorities, reverse=True), 0
    while True:
        for i, priority in enumerate(priorities):
            s = search[c]
            if priority == s:
                c += 1
                answer += 1
                if i == location:
                    break
        else:
            continue
        break
    return answer
  • sorted된 list를 통해서 max를 순차적으로 비교해주고 있다. 그리고 이 list를 비교할때 c를 이용해서 cursor처럼 단계단계 옮겨주고 있다.
  • 전체적으로 내 풀이보다 compact하게 구성을 잘한것 같다.

다른 풀이3 🌳

def solution(priorities, location):
    jobs = len(priorities)
    answer = 0
    cursor = 0
    while True:
        if max(priorities) == priorities[cursor%jobs]:
            priorities[cursor%jobs] = 0
            answer += 1
            if cursor%jobs == location:
                break
        cursor += 1
    return answer
  • 위의 코드는 list를 새로 생성해주는 것이 아니라 cursor% jobs를 통해서 list가 내부적으로 반복되도록 나머지를 적절하게 이용해주었다!!
  • 그리고 만약 그 index값이 location과 일치하면 while문을 탈출하면 되니까 정말 효율적인 IDEA로 잘 짠 code인것 같다.

다른 풀이4 🌳

def solution(priorities, location):
    answer = 0
    from collections import deque

    d = deque([(v,i) for i,v in enumerate(priorities)])

    while len(d):
        item = d.popleft()
        if d and max(d)[0] > item[0]:
            d.append(item)
        else:
            answer += 1
            if item[1] == location:
                break
    return answer
  • 여기서 주목해야할것은collections 모듈로부터 deque를 받아와서 popleft() method를 사용해줬다는 것이다. 기존에 pop(0)O(n)만큼 시간이 걸리지만 popleft()O(1)만큼 시간이 걸리므로 deque를 이용해서 효율성을 향상시킨 코드이다.