[2023 KAKAO BLIND RECRUITMENT] - 택배 배달과 수거하기 파이썬 (python)

김준엽·2023년 2월 1일
0

알고리즘

목록 보기
2/5
post-thumbnail

문제

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

문제 설명

당신은 일렬로 나열된 n개의 집에 택배를 배달하려 합니다. 배달할 물건은 모두 크기가 같은 재활용 택배 상자에 담아 배달하며, 배달을 다니면서 빈 재활용 택배 상자들을 수거하려 합니다.
배달할 택배들은 모두 재활용 택배 상자에 담겨서 물류창고에 보관되어 있고, i번째 집은 물류창고에서 거리 i만큼 떨어져 있습니다. 또한 i번째 집은 j번째 집과 거리 j - i만큼 떨어져 있습니다. (1 ≤ ijn)
트럭에는 재활용 택배 상자를 최대 cap개 실을 수 있습니다. 트럭은 배달할 재활용 택배 상자들을 실어 물류창고에서 출발해 각 집에 배달하면서, 빈 재활용 택배 상자들을 수거해 물류창고에 내립니다. 각 집마다 배달할 재활용 택배 상자의 개수와 수거할 빈 재활용 택배 상자의 개수를 알고 있을 때, 트럭 하나로 모든 배달과 수거를 마치고 물류창고까지 돌아올 수 있는 최소 이동 거리를 구하려 합니다. 각 집에 배달 및 수거할 때, 원하는 개수만큼 택배를 배달 및 수거할 수 있습니다.

다음은 cap=4 일 때, 최소 거리로 이동하면서 5개의 집에 배달 및 수거하는 과정을 나타낸 예시입니다.

배달 및 수거할 재활용 택배 상자 개수

집 #1 집 #2 집 #3 집 #4 집 #5
배달 1개 0개 3개 1개 2개
수거 0개 3개 0개 4개 0개

배달 및 수거 과정

집 #1 집 #2 집 #3 집 #4 집 #5 설명
남은 배달/수거 1/0 0/3 3/0 1/4 2/0 물류창고에서 택배 3개를 트럭에 실어 출발합니다.
남은 배달/수거 1/0 0/3 3/0 0/4 0/0 물류창고에서 5번째 집까지 이동하면서(거리 5) 4번째 집에 택배 1개를 배달하고, 5번째 집에 택배 2개를 배달합니다.
남은 배달/수거 1/0 0/3 3/0 0/0 0/0 5번째 집에서 물류창고까지 이동하면서(거리 5) 4번째 집에서 빈 택배 상자 4개를 수거한 후, 수거한 빈 택배 상자를 물류창고에 내리고 택배 4개를 트럭에 싣습니다.
남은 배달/수거 0/0 0/3 0/0 0/0 0/0 물류창고에서 3번째 집까지 이동하면서(거리 3) 1번째 집에 택배 1개를 배달하고, 3번째 집에 택배 3개를 배달합니다.
남은 배달/수거 0/0 0/0 0/0 0/0 0/0 3번째 집에서 물류창고까지 이동하면서(거리 3) 2번째 집에서 빈 택배 상자 3개를 수거한 후, 수거한 빈 택배 상자를 물류창고에 내립니다.

16(=5+5+3+3)의 거리를 이동하면서 모든 배달 및 수거를 마쳤습니다. 같은 거리로 모든 배달 및 수거를 마치는 다른 방법이 있지만, 이보다 짧은 거리로 모든 배달 및 수거를 마치는 방법은 없습니다.

트럭에 실을 수 있는 재활용 택배 상자의 최대 개수를 나타내는 정수 cap, 배달할 집의 개수를 나타내는 정수 n, 각 집에 배달할 재활용 택배 상자의 개수를 담은 1차원 정수 배열 deliveries와 각 집에서 수거할 빈 재활용 택배 상자의 개수를 담은 1차원 정수 배열 pickups가 매개변수로 주어집니다. 이때, 트럭 하나로 모든 배달과 수거를 마치고 물류창고까지 돌아올 수 있는 최소 이동 거리를 return 하도록 solution 함수를 완성해 주세요.


접근방식

우선 일직선 상의 배열 상에서 최소 거리를 구하는 문제이다.
단정 지으면 안되지만, 이런 문제에서는 다이나믹 프로그래밍(DP) 와 그리디 알고리즘이 떠오른다. 거기 추가로 BFS와 같은 그래프 탐색을 적용하는 경우도 있다.
이 문제의 조건은 크게

  • 택배를 실을 수 있는 수가 정해져 있다.
  • 각 집에는 배달할 택배의 개수와 수거 할 택배의 박스가 있다.
  • 배달 택배와 수거하는 택배는 동일한 택배로 본다.
  • 0번 인덱스에서 출발하여 택배박스를 0번에 다시 놓기 위해 왕복을 해야한다.

문제를 보면 N도 충분히 크지 않고 그리디는 떠올리기 어렵지는 않았던 것 같다.
근데 택배의 개수최소 거리 둘 중 어느 요소에 그리디 알고리즘을 적용해야하는지를 나는 어려워했었다.
정답은 둘 다 였다..
카카오의 해설에서는 이를 stack을 활용한 그리디 알고리즘이라고 설명한다.
문제 해결의 키포인트는 두가지 이다.

  1. 오른쪽으로 이동할 때 최대한 많은 택배를 배달하고, 돌아올때는 최대한 많은 택배를 수거한다.
  2. 가장 멀리 있는 집 부터 배달하고, 수거한다.

이를 stack 으로 구현한다면,
1. 배달 스택, 수거 스택 2가지의 스택을 만든다.
2. 택배가 0개보다 많은 집의 인덱스를 0번 인덱스부터 스택에 차례대로 넣는다.
3. 그러면 스택의 최상단(top)에는 가장 멀리있는 집이 있다.
4. 택배를 배달하러 출발할 집을 초기에 정할때는 배달 스택과 수거 스택 최상단을 비교하여, 더 큰 집을 가장 멀리 갈 집으로 정한다.

위의 알고리즘을 적용하면 오른쪽으로 이동하면서 가장 많은 택배를 배달하고, 돌아오면서 가장 많은 택배를 수거할 수 있다.

정답코드

def solution(cap, n, deliveries, pickups):
    answer = 0
    deliver_stack, pickup_stack = [], []
    for i in range(n):
        if deliveries[i] != 0:
            deliver_stack.append(i)
        if pickups[i] != 0:
            pickup_stack.append(i)
    while deliver_stack or pickup_stack:
        flag = cap
        if len(deliver_stack) == 0 and len(pickup_stack) != 0:
            answer += (pickup_stack[-1] + 1) * 2
        elif len(deliver_stack) != 0 and len(pickup_stack) == 0:
            answer += (deliver_stack[-1] + 1) * 2
        elif deliver_stack[-1] >= pickup_stack[-1]:
            answer += (deliver_stack[-1] + 1) * 2
        else:
            answer += (pickup_stack[-1] + 1) * 2
        while deliver_stack and flag != 0:
            index = deliver_stack.pop()
            if deliveries[index] <= flag:
                flag -= deliveries[index]
                deliveries[index] = 0
            else:
                deliveries[index] -= flag
                flag = 0
                deliver_stack.append(index)
        flag = cap - flag
        while pickup_stack and flag != 0:
            index = pickup_stack.pop()
            if pickups[index] <= flag:
                flag -= pickups[index]
                pickups[index] = 0
            else:
                pickups[index] -= flag
                flag = 0
                pickup_stack.append(index)
    return answer

코드해석

        if len(deliver_stack) == 0 and len(pickup_stack) != 0:
            answer += (pickup_stack[-1] + 1) * 2
        elif len(deliver_stack) != 0 and len(pickup_stack) == 0:
            answer += (deliver_stack[-1] + 1) * 2
        elif deliver_stack[-1] >= pickup_stack[-1]:
            answer += (deliver_stack[-1] + 1) * 2
        else:
            answer += (pickup_stack[-1] + 1) * 2

초기에 택배 차가 갈 가장 멀리 있는 집을 정하는 과정이다.
수거해야할 집과 배달할 집 중 한번에 갈때 거리가 제일 큰 집으로 이동해야하고, 왕복을 해야하므로 *2를 해주어야한다.

        while deliver_stack and flag != 0:
            index = deliver_stack.pop()
            if deliveries[index] <= flag:
                flag -= deliveries[index]
                deliveries[index] = 0
            else:
                deliveries[index] -= flag
                flag = 0
                deliver_stack.append(index)
        # 돌아올때 택배를 수거하기 위해 택배차의 공간을 초기화해준다.        
        flag = cap - flag

오른쪽으로 이동하면서 최대한 많은 택배를 배달 하는 코드이다.
deliver_stack에 멀리있는 택배부터 배달을 하지만 flag = cap 최대 수용량을 넘지 않게 신경써주는 것도 중요하다.

        while pickup_stack and flag != 0:
            index = pickup_stack.pop()
            if pickups[index] <= flag:
                flag -= pickups[index]
                pickups[index] = 0
            else:
                pickups[index] -= flag
                flag = 0
                pickup_stack.append(index)

이제 돌아오면서 수거할 택배의 수를 정하는 과정이다.
이렇게 한 싸이클이 끝나고, 택배 스택과 수거 스택에 남아있는 택배가 있다면 위의 과정을 반복하고, 남아있는 택배가 없다면 배달이 됐으므로 answer를 return 해주면 된다.

profile
꾸준히 성장하는 개발자 지망생

0개의 댓글