Coding/알고리즘

[알고리즘 TIL] 택배 배달과 수거하기

kangplay 2025. 2. 28. 17:14
문제

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

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

프로그래머스 LV2 : 시뮬레이션

설명

이 문제는 LV2 문제로, 특별한 알고리즘을 요하는 문제는 아니지만, 쓸데없는 연산과 변수가 선언되면 실패하는 까다로운 문제였다. 

트럭 하나로 배달/수거를 마치고 물류 창고까지 돌아올 수 있는 최소 이동 거리를 구해야한다. 이동 거리가 최소가 되기 위해서는 가장 멀ㄹ리 있는 집부터 배달/수거를 완료해서 다시 그 집을 가지 않도록 만들었다. 

class Solution {
    public long solution(int cap, int n, int[] deliveries, int[] pickups) {
        long answer = 0;
        int lastDeliveryCount = 0;
        int lastPickupCount = 0;

        //현재 남은 배달 갯수와 수거 갯수 저장
        for (int i = 0; i < n; i++) {
            lastDeliveryCount += deliveries[i];
            lastPickupCount += pickups[i];
        }

        while (lastDeliveryCount != 0 || lastPickupCount != 0) {
            //물류창고에서 cap과 낭믄 배달 갯수 중 더 작은 갯수만큼 챙김
            int lastCap = Math.min(cap, lastDeliveryCount);
            //먼 곳부터 배달
            int farthestDeliveryNum = 0;
            for (int i = n - 1; i >= 0; i--) {
                if (deliveries[i] > 0) {
                    if (farthestDeliveryNum == 0) farthestDeliveryNum = i+1;
                    //배달할 수 있는 마지막집인 경우
                    if (lastCap <= deliveries[i]) {
                        deliveries[i] -= lastCap;
                        lastDeliveryCount -= lastCap;
                        lastCap = 0;
                        break;
                    }//아직 택배 상자가 남은 경우
                    else {
                        lastCap -= deliveries[i];
                        lastDeliveryCount -= deliveries[i];
                        deliveries[i] = 0;
                    }
                }
            }
            //택배에 남은 공간
            int lastSpace = cap;
            //먼 곳부터 수거
            int farthestPickupNum = 0;
            for (int i = n - 1; i >= 0; i--) {
                if (pickups[i] > 0) {
                    if (farthestPickupNum == -0) farthestPickupNum = i+1;
                    //수거할 수 있는 마지막집인 경우
                    if (lastSpace <= pickups[i]) {
                        pickups[i] -= lastSpace;
                        lastPickupCount -= lastSpace;
                        lastSpace = 0;
                        break;
                    }//아직 트럭에 공간이 남은 경우
                    else {
                        lastSpace -= pickups[i];
                        lastPickupCount -= pickups[i];
                        pickups[i] = 0;
                    }
                }
            }
            //배달과 수거 중 더 멀리 간 곳을 결과에 더함
            answer += 2L * (Math.max(farthestDeliveryNum, farthestPickupNum));
        }
        return answer;
    }
}

기본적인 로직은 맞았지만, 테스트 20개 중 2개가 시간 초과로 실패했다. 배달과 수거를 진행할 때, 서로 다른 반복문에서 진행해서 그런가..? 해서 같은 루프에서 배달과 수거를 동시에 진행하는 방향으로 수정하였다.

class Solution {
    public long solution(int cap, int n, int[] deliveries, int[] pickups) {
        long answer = 0;
        int lastDeliveryCount = 0;
        int lastPickupCount = 0;

        //현재 남은 배달 갯수와 수거 갯수 저장
        for (int i = 0; i < n; i++) {
            lastDeliveryCount += deliveries[i];
            lastPickupCount += pickups[i];
        }

        //배달 또는 수거할 집이 하나라도 남았으면 반복
        while (lastDeliveryCount > 0 || lastPickupCount > 0) {
            //물류창고에서 cap과 남은 배달 갯수 중 더 작은 갯수만큼 챙김
            int lastCap = Math.min(cap, lastDeliveryCount);
            //택배에 남은 공간
            int lastSpace = cap;
            //먼 곳부터 배달
            int farthestHouse = 0;
            //배달과 수거를 동시에 진행
            for (int i = n - 1; i >= 0; i--) {
                if (deliveries[i] > 0 || pickups[i] > 0) {
                    //배달 또는 수거를 진행할 제일 먼 집 위치 저장
                    if (farthestHouse == 0) farthestHouse = i + 1;
                    //배달 처리
                    if (deliveries[i] > 0) {
                        int deliverAmount = Math.min(lastCap, deliveries[i]);
                        deliveries[i] -= deliverAmount;
                        lastDeliveryCount -= deliverAmount;
                        lastCap -= deliverAmount;
                    }
                    //수거 처리
                    if (pickups[i] > 0) {
                        int pickupAmount = Math.min(lastSpace, pickups[i]);
                        pickups[i] -= pickupAmount;
                        lastPickupCount -= pickupAmount;
                        lastSpace -= pickupAmount;
                    }
                }
                if (lastCap == 0 && lastSpace == 0) break;
            }
            answer += 2L * farthestHouse;
        }
        return answer;
    }​

코드는 간결해졌지만, 시간 복잡도는 해결되지 않았다.

택배와 수거를 한 바퀴 진행하고 나면, 다시 택배와 수거를 진행하는 코드(while과 for문, 이중반복문)이므로, O(N^2)의 시간복잡도이다. 

 

다른 블로그의 코드를 참고하니, 단 한 바퀴만으로, 즉 O(N)으로 답을 구해냈다.

  • 가장 멀리 떨어진 집부터 방문한다.
  • 해당 집에 배달/수거할 박스가 없으면 그냥 패스한다.(continue)
  • 배달/수거할 박스가 있으면 해당 집을 몇 번 왔다갔다해야하는지 구해서 이동 거리에 반영해준다.
  • 이후, 배달할 수 있는 택배, 혹은 수거할 수 있는 공간이 남았으면 정보를 기록해준다. (남았으면 이 전 집에서 되돌아가는 길에 처리할 수 있기 때문)
  • 남은 배달 혹은 수거 갯수를 고려하여 반복한다.

어떻게 이런 생각을 하지.. 더 열심히 해야겠다...

구현

public class Q_택배배달과수거하기_시뮬레이션 {
    public static void main(String[] args) {
        Solution solution = new Solution();
        int cap = 4;
        int n = 5;
        int[] deliveries = new int[]{1, 0, 3, 1, 2};
        int[] pickups = new int[]{0, 3, 0, 4, 0};
        long answer = solution.solution(cap, n, deliveries, pickups);
        System.out.println(answer);
    }

}

class Solution {
    public long solution(int cap, int n, int[] deliveries, int[] pickups) {
        long answer = 0;
        int delivery = 0;
        int pickup = 0;

        for (int i = n - 1; i >= 0; i--) {
            //더 먼 집을 왔다갔다하며 처리한 집이여도, 처리한 갯수는 delivery와 pickup 변수에 갱신해주어야함
            //따라서 배달, 수거할 게 없는 집만 패스
            if (deliveries[i]==0 && pickups[i]==0) continue;
            //해당 집을 몇 번 왔다갔다해야하는지 구함
            int count = 0;
            while (delivery < deliveries[i] || pickup < pickups[i]) {
                count++;
                //왔다갔다하는 과정에서 만약 배달만 하는 경우에는 다른 집을 수거할 수도, 수거만 하는 경우에는 다른 집을 배달할 수 있음
                //따라서 수용 가능한 공간만큼 더하는 것임
                delivery += cap;
                pickup += cap;
            }
            //아래 계산 후 남은 값은 이미 오며가며 추가적으로 처리한 배달, 수거 갯수이므로 다음 집에 영향을 줌
            delivery -= deliveries[i];
            pickup -= pickups[i];
            answer += (long) (i+1) * count * 2;
        }
        return answer;
    }
}