백준 1092 배 java
1. 문제에 대하여 📦
- 크레인과 박스에 무게가 존재한다.
- 크레인은 자신보다 무거운 박스를 들 수 없다.
- 모든 크레인은 한 번에 하나의 박스밖에 운반하지 못하고, 자신보다 가벼운 박스를 옮기는데 무조건 1분이 든다.
이때 모든 박스를 옮기는 데 드는 최소 시간은?
만약 아무리 시간이 지나도 모든 박스를 옮기지 못하면 -1 출력
만약 아무리 시간이 지나도 모든 박스를 옮기지 못하면 -1 출력
2. 코드가 나오기까지 🛠️
KEY WORD: 이분 탐색, 매개변수 탐색이분탐색 그 중에서도 최적화 문제를 여러 개의 결정문제로 바꾸어 푸는 매개변수 탐색을 생각했다.
해당 문제에서는 박스를 다 옮길 수 있는 모든 시간대에서 최소 시간대를 구하는 것이기 때문에 최적화 문제에 해당한다. 해당 최적화 문제를 '안수로 들어온 n 분안에 모든 박스를 옮길 수 있는가?' 라는 결정 문제로 바꾸어서 풀었다.
해당 문제에서는 박스를 다 옮길 수 있는 모든 시간대에서 최소 시간대를 구하는 것이기 때문에 최적화 문제에 해당한다. 해당 최적화 문제를 '안수로 들어온 n 분안에 모든 박스를 옮길 수 있는가?' 라는 결정 문제로 바꾸어서 풀었다.
모두 옮길 수 있으면 O, 아니면 X로 두고 n분 안에 다 옮긴다면 하향 조정하여 시간을 줄이고, 아니라면 늘렸다.
이때 이분탐색의 각 포인터가 교차할 때, 왼쪽 포인터(작은 값에서 높은 값으로 올라가는 포인터)의 위치가 답이다.
이때 이분탐색의 각 포인터가 교차할 때, 왼쪽 포인터(작은 값에서 높은 값으로 올라가는 포인터)의 위치가 답이다.
(1) 시간복잡도 분석 ⏳
최대 10,000 개의 박스가 존재할 수 있으므로, 그것을 한 번에 하나씩 처리하는 최악의 경우의 수를 생각해보면, 10,000 분에 모든 일을 처리할 것이다. 따라서 이분탐색은 10,000을 대상으로 할 것이며, 한 번의 탐색 당 크레인과 박스를 대조하는 이중 반복문을 할 것이니, 50 x 10,000번을 더할 것이다. 따라서 시간 복잡도는
일 것이다.
3. 코드 🌐
(1) SUDO CODE 💬
1. 1분부터 10,000분까지를 기점으로 이분 탐색하기 (start = 1, end = 10,000) 2. 현재 찾은 mid 값으로 모든 박스를 옮길 수 있는지 체크 (1) 되면 mid 하향 조정 (2) 안되면 mid 상향 조정 3. start의 위치가 바로 답이다.
(2) JAVA CODE ☕
import java.util.*; import java.io.*; // b 17255 N으로 만들기 public class Main { static int N,M; static ArrayList<Integer> cranes = new ArrayList<>(); static ArrayList<Integer> boxes = new ArrayList<>(); public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); N = Integer.parseInt(br.readLine()); StringTokenizer st = new StringTokenizer(br.readLine()); for(int i = 0; i < N; i++){ cranes.add(Integer.parseInt(st.nextToken())); } M = Integer.parseInt(br.readLine()); st = new StringTokenizer(br.readLine()); for(int i = 0; i < M; i++){ boxes.add(Integer.parseInt(st.nextToken())); } cranes.sort(Collections.reverseOrder()); boxes.sort(Collections.reverseOrder()); System.out.println(binary_search()); } public static int binary_search(){ int start = 0; int end = 10_001; while (start <= end){ int mid = (start + end)/2; if(isRight(mid)) end = mid-1; else start = mid+1; } return start > 10_001 ? -1 : start; } public static boolean isRight(int round){ int cnt = 0; boolean [] isVisited = new boolean[M]; for(int k = 0; k < round; k++){ int j = 0; for(int i = 0; i < N; i++){ int now_crane = cranes.get(i); while ( j < M && (boxes.get(j) > now_crane || isVisited[j])) j++; if( j < M && boxes.get(j) <= now_crane) { isVisited[j] = true; cnt++; } } if(cnt == M) return true; }// System.out.printf("round: %d,%s\\n", round, Arrays.toString(isVisited)); return false; } }
4. 트러블 슈팅 or 배운 점📝
(1) 우선순위 큐 풀이 했다가 시간초과남.
근데 우선순위 큐에 마냥 값을 집어넣는 게 아니라 정확히 M개만 처리했을 뿐인데, 시간초과가 왜 났는지 아직 이해가 안된다. 전체 코드는 다음과 같다.
import java.util.*; import java.io.*; // b 17255 N으로 만들기 public class Main { static int N, M; static ArrayList<Integer> cranes = new ArrayList<>(); static PriorityQueue<Integer> boxes = new PriorityQueue<>((a,b) -> b-a); public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); N = Integer.parseInt(br.readLine()); StringTokenizer st = new StringTokenizer(br.readLine()); for(int i = 0; i < N; i++){ cranes.add(Integer.parseInt(st.nextToken())); } M = Integer.parseInt(br.readLine()); st = new StringTokenizer(br.readLine()); for(int i = 0; i <M; i++){ int now =Integer.parseInt(st.nextToken()); boxes.add(now); } cranes.sort(Collections.reverseOrder()); int time = 0; ArrayList<Integer> temp = new ArrayList<>(); while (!boxes.isEmpty()){ for(int i = 0; i < cranes.size(); i++){ int now_crane = cranes.get(i); if(i == 0 && !boxes.isEmpty() && now_crane < boxes.peek()) { System.out.println(-1); // 첫 타자인데, 가장 무거운 크레인보다 무거우면 못 듦 -> 그냥 나가기 return; } while (!boxes.isEmpty() && now_crane < boxes.peek()){ int now_box = boxes.poll(); temp.add(now_box); } if(!boxes.isEmpty() && now_crane >= boxes.peek()) boxes.poll(); } time++; boxes.addAll(temp); temp.clear(); } System.out.println(time); } }
(2) 투 포인터 쓰면 더 쉬움
내 풀이는 시간이 3초가 걸려서 시간초과를 간당간당하게 피한 듯 하다. 투 포인터로 이중 반복문 한 번에 다 해결한 풀이의 경우 나보다 15배 정도 빨랐다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); List<Integer> cranes = new ArrayList<>(); String[] tokens = br.readLine().split(" "); for (int i = 0; i < n; i++) { cranes.add(Integer.parseInt(tokens[i])); } cranes.sort((o1, o2) -> o2 - o1); int m = Integer.parseInt(br.readLine()); tokens = br.readLine().split(" "); List<Integer> boxs = new ArrayList<>(); for (int i = 0; i < m; i++) { boxs.add(Integer.parseInt(tokens[i])); } boxs.sort((o1, o2) -> o2 - o1); int result = move(cranes, boxs); System.out.println(result); } private static int move(List<Integer> cranes, List<Integer> boxs) { int count = 0; if (cranes.get(0) < boxs.get(0)) { return -1; } while (!boxs.isEmpty()) { count++; int craneIndex = 0; int boxIndex = 0; while (craneIndex < cranes.size()) { if (boxIndex >= boxs.size()) { break; } if (cranes.get(craneIndex) >= boxs.get(boxIndex)) { boxs.remove(boxIndex); craneIndex++; } else { boxIndex++; } } } return count; } }
해당 풀이에서는 ArrayList의 index를 활용하여 지우고, 다음 값으로 지시자를 이동안해도 땡겨지도록 해서 문제를 풀었다. 나는 저 풀이가 컬렉션 프레임워크의
fail-fast에 걸린다고 생각했는데, 아니었다.fail-fast에 걸리는 경우는
- iterator 가 가리키고 있지 않은 쪽을 삭제
- for-each 문에서 값을 즉시 삭제
하는 경우이다.
위처럼 iterator도 아니고, value 직접 삭제도 아닌 '인덱스를 활용한 삭제' 는 fast-fail에 걸리지 않는다.