Q 22988 재활용 캠페인

💡 문제 요약 및 분석

빈 헤어 에센스 용기를 가져다 주면 새로운 용기에 헤어 에센스를 채워주는 가게가 있다. (에센스를 채워주는 기준은 아래와 같다) 히나가 N개의 용기를 가져갈 때 꽉 찬 헤어 에센스 용기를 최대 몇 개 만들 수 있는지 출력하자.

  • Aml, Bml 용량이 남은 헤어 에센스 용기 두 개를 반납하면 A + B + X/2 만큼 새로운 용기에 헤어 에센스를 채워준다.

  • 새로운 용기에 담는 헤어 에센스는 총 용량 Xml 를 초과하지 않는다.

💡 알고리즘 설계

✅ 핵심 아이디어 : 최대한 남은 용량이 적은 것들끼리 모아서 반납하는 것이 이득이다. 배열을 정렬한 후 계산하자.

  1. 입력

    • 표준입력으로 int N, int X 생성.
    • 표준입력을 받아 List bottles 배열 생성. 입력값 == X 인 경우 count++ 하고 리스트에 넣지 않는다.
    • 해당 배열을 오름차순 정렬한다.
  2. 연산

    • left = 0, right = 1
    • while(right < bottles.size) 까지 반복하면서 아래 로직을 수행한다.
    • int recycle = Math.min(bottles.get(left) + bottles.get(right) + X/2, X)
    • recycle == X : count++
    • recycle < M : recycle 값을 bottles 리스트의 마지막에 추가
    • left += 2, right += 2;
  3. 출력

    • count 변수를 출력한다.

💡 코드

import java.util.*;

public class Main {

    static int read() throws Exception {
        int c = 0, n = 0;
        while ((c = System.in.read()) > 47) n = (n << 3) + (n << 1) + (c & 15);
        return n;
    }

    public static void main(String[] args) throws Exception {
        int N = read();
        int X = read();
        List<Integer> bottles = new ArrayList<>();
        int count = 0;
        for (int i = 0; i < N; i++) {
            int input = read();
            if (input == X) {
                count++;
            } else {
                bottles.add(input);
            }
        }
        Collections.sort(bottles);

        int left = 0, right = 1;
        while (right < bottles.size()) {
            int recycle = Math.min(bottles.get(left) + bottles.get(right) + X/2, X);
            if (recycle == X) {
                count++;
            } else if (recycle < X) {
                bottles.add(recycle);
            }
            left += 2;
            right += 2;
        }
        System.out.println(count);
    }

}

💡 틀린 부분 분석

계속 수정해봐도 틀려서 로직 자체의 문제가 아닐까 의심하게 되었다. 그래서 정답을 찾아보니 애초에 나의 접근 방식이 틀렸다는 것을 알게 되었다. 이 문제는 투 포인터를 활용해서 풀어낼 수 있다. 일단 최적의 답을 구하기 위한 조건은 다음과 같다.

  1. 남은 용량이 많은 용기와 적은 용기를 합치는 것이 좋다. 왜냐하면 기본적으로 X/2 용량을 확보한 상태에서 두 용기의 남은 용량을 더해주기 때문에, 남은 X/2 용량을 한 번에 채울 수 있다면 최적의 조합일 것이다. 이 경우 우리가 생각해야할 min 값은 다음과 같다.

    • X 가 홀수인 경우 : min == X/2 + 1 (주어지는 수가 정수이기 때문에 나누면 0.5가 남을 것이다. 이를 채워주기 위해 +1 한다.)
    • X 가 짝수인 경우 : min == X/2
  2. 헤어 에센스 용기가 3개 있으면 가득 찬 용기 하나를 얻을 수 있다. 왜냐하면 두 개를 합치면 X/2 용량이 생기고, 세 번째 용기의 남은 양이 어떻든 X/2 용기와 합치면 다시 X/2 용량이 생기기 때문에 (X/2) + (X/2) = X 로 온전한 한 병이 된다.

  3. 애초에 X 만큼의 용량을 가진 병은 이미 온전한 한 병이므로 다른 병과 합치지 않아도 된다.

💡 알고리즘 재설계 및 정답 코드

  • 표준입력으로 입력받은 배열을 오름차순으로 정렬한다.
  • left[0], right[length - 1] 에서 출발한다.
  • 다음과 같은 조건에 따라 포인터를 움직인다. L + R >= min : count++, L++, R-- L + R < min : L++
  • 두 포인터가 같은 원소를 가리킬 때 종료된다.
  • 남은 병의 개수 / 3 으로 나눈 값을 count 변수에 추가한다. (왜냐하면 용기 3개만 있으면 용량과 관계없이 온전한 한 병이 만들어지므로)
  • count 변수를 출력한다.
import java.util.*;

public class Main {

    static long read() throws Exception {
        long c = 0, n = 0;
        while ((c = System.in.read()) > 47) n = (n << 3) + (n << 1) + (c & 15);
        return n;
    }

    public static void main(String[] args) throws Exception {
        int N = (int)read();
        long X = read();
        long min = (X + 1) / 2;

        long[] bottles = new long[N];
        int bottleCount = 0;
        int count = 0;
        
        for (int i = 0; i < N; i++) {
            long input = read();
            if (input >= X) {
                count++;
            } else {
                bottles[bottleCount++] = input;
            }
        }
        
        Arrays.sort(bottles, 0, bottleCount);

        int left = 0;
        int right = bottleCount - 1;
        int remain = bottleCount;

        while (left < right) {
            long sum = bottles[left] + bottles[right];
            if (sum >= min) {
                count++;
                left++;
                right--;
                remain -= 2;
            } else {
                left++;
            }
        }

        count += remain / 3;
        System.out.println(count);
    }
}

💡 시간 복잡도, 공간 복잡도

시간 복잡도공간 복잡도
O(n)O(n)

💡 느낀점 및 기억할 정보

메모리 사용량 주의 !!

분명 로직은 맞는데 계속 틀렸다고 해서 설마 메모리가 부족한가…? 싶어서 List –> 배열 로 바꿔보았더니 성공했다. 문제에서 주어진 메모리가 1024mb 로 이 정도면 넉넉하다고 생각했는데 설마 이게 원인일 줄은…