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

#59 알고리즘 연습 - 기능개발(Python)

기능개발

프로그래머스 팀에서는 기능 개선 작업을 수행 중입니다. 각 기능은 진도가 100%일 때 서비스에 반영할 수 있습니다.

또, 각 기능의 개발속도는 모두 다르기 때문에 뒤에 있는 기능이 앞에 있는 기능보다 먼저 개발될 수 있고, 이때 뒤에 있는 기능은 앞에 있는 기능이 배포될 때 함께 배포됩니다.

먼저 배포되어야 하는 순서대로 작업의 진도가 적힌 정수 배열 progresses와 각 작업의 개발 속도가 적힌 정수 배열 speeds가 주어질 때 각 배포마다 몇 개의 기능이 배포되는지를 return 하도록 solution 함수를 완성하세요.

제한 사항

  • 작업의 개수(progresses, speeds배열의 길이)는 100개 이하입니다.
  • 작업 진도는 100 미만의 자연수입니다.
  • 작업 속도는 100 이하의 자연수입니다.
  • 배포는 하루에 한 번만 할 수 있으며, 하루의 끝에 이루어진다고 가정합니다. 예를 들어 진도율이 95%인 작업의 개발 속도가 하루에 4%라면 배포는 2일 뒤에 이루어집니다.

입출력 예

progresses speeds return
[93,30,55] [1,30,5] [2,1]

입출력 예 설명

첫 번째 기능은 93% 완료되어 있고 하루에 1%씩 작업이 가능하므로 7일간 작업 후 배포가 가능합니다. 두 번째 기능은 30%가 완료되어 있고 하루에 30%씩 작업이 가능하므로 3일간 작업 후 배포가 가능합니다. 하지만 이전 첫 번째 기능이 아직 완성된 상태가 아니기 때문에 첫 번째 기능이 배포되는 7일째 배포됩니다. 세 번째 기능은 55%가 완료되어 있고 하루에 5%씩 작업이 가능하므로 9일간 작업 후 배포가 가능합니다.

따라서 7일째에 2개의 기능, 9일째에 1개의 기능이 배포됩니다.

문제풀이 IDEA

뭔가 예전에는 stack , queue 이런것을 생각 안하고 풀다보니까 이 문제를 어떻게 물어야할지, 시간이 흘러감에 따라서 list의 각 원소를 += 해줘야하고 100이 넘으면 또 체크해줘야하고 어렵게 생각했었다.

하지만 생각을 조금만 바꾸니 progresses의 앞부분부터 순차적으로 탐색을 하면서 앞의 작업이 완료되었으면 이후로 완료된 작업과 같이 packaging해서 cnt를 리턴해주면 될 것같다는 생각이 들었다.

그래서 queue를 이용해서 풀고자 python에서 queue를 modele로 가져와서 사용하는 것을 찾아보니 내가 풀고자 하는 플로우와 비슷하지만 약간 다른면이 있어서

list가 앞부분부터 탐색하고 조작하기 힘든 부분을 뒤집어서 해결해주기로 하였다.

내 풀이 🌱

def solution(arr1, arr2):
    answer = list()
    rev_prog = list(reversed(arr1))
    rev_speed = list(reversed(arr2))

    while rev_prog:
        for i in range(len(rev_prog)):
            rev_prog[i] += rev_speed[i]

        cnt = 0
        while rev_prog[-1] >= 100:
            rev_prog.pop()
            cnt += 1

            if not rev_prog:
                break

        if cnt != 0:
            answer.append(cnt)

    return answer

위의 코드를 간단하게 풀어보면 list가 앞부분부터 조작하기에는 시간 복잡도도 오래걸리고 힘든 부분이 있어서 처음부러 reversed를 이용해서 뒤집어주고,

while문을 이용해서 process가 남아있으면 계속 반복을 하게 해줘, 편하게 반복문의 조건을 걸어주었다. 그리고 index값이 필요해서 len을 이용해주고, 이때의 len은 rev_prog가 while한번 돌때마다 업데이트가 되니까 for문의 index도 가변적으로 변한단 것을 체크 할수 있다.

그리고 밑에서는 [-1]을 통해서 가장 마지막 인덱스 ( 다른말로하면 이게 원래 첫번째 순서의 index였다) 가 기능개발이 완료되면 제거하고 cnt해준다음, 다시금 while을 통해서 다시 마지막 index를 탐색해 나가는 과정을 거친다.

그래서 전체적으로 보면 while을 효율적으로 사용함으로서 코드가 조금더 직관적이고 문제 해결이 쉽지 않았나싶다.

다른 풀이 🌳

def solution(progresses, speeds):
    Q=[]
    for p, s in zip(progresses, speeds):
        if len(Q)==0 or Q[-1][0]<-((p-100)//s):
            Q.append([-((p-100)//s),1])
        else:
            Q[-1][1]+=1
    return [q[1] for q in Q]

음.. 위의 코드를 처음 봤을 때는 잘 이해가 되지 않았다. 효율성 측면에서는 좋은코드가 틀림없지만 가독성 측면에서는 약간 아쉬운게 아닌가싶다.

그래서 전체적으로 코드를 해석해보면

  • zip을 이용해서 하나의 for문을 돌림과 동시에 잔여일의 양과 일처리 속도를 들고와서 사용해주고있다.

그 다음 코드들은 아이디어를 잘 비틀어 생각한 모습인데, Q에는 지금 걸리는 시간과 그 동안 쌓이는 일을 담아주고 있다.

Q가 비어있으면 처음 시작하는 일이기떄문에 무조건 추가를 해주게 되는데 이때 그냥 추가해주는게 아닌 100 - 진행상황 = 남은양 // 속도 를 이용해 주고 있다. 이때 100-p가 아닌 p-100으로 남은 상황을 계산하는 것은 뒤에 따라오는 // 연산을 해주게 될때 -부호를 가지고 있으면 자동으로 올림이 되기 때문이라한다(프로그래머스의 고수분들께서 댓글로 알려주셨다).

그리고 이를 s로 나누게 되면 걸리는 시간이 나오는데 Q에 들어가는 모양은 [걸리는 시간, 그 동안의 일] 모양이 된다. 따라서 첫번째 if문에서 만약 현재 Q에 저장되어있는 일보다 시간이 덜 걸린다면, 이미 Q에 저장되어있는 [걸리는 시간, 그 동안의 일], 그 동안의 일에 +1을 해주는 방식으로 진행을 해주었다.

해당 풀이를 보고 든 생각은 어떻게 사고의 전환을 이렇게 해서 효율적인 코드를 짤 수 있었을까하는 신기함이었다. 이번 문제를 통해서 다소 직관적으로 코드를 짜는 방법을 연습했는데, 다른 풀이를 보면서 한발 더 생각하는 연습을 할 수 있었던 좋은 경험이었다.