[이분탐색] 프로그래머스 - 예산
Updated:
문제 설명
국가의 역할 중 하나는 여러 지방의 예산요청을 심사하여 국가의 예산을 분배하는 것입니다. 국가예산의 총액은 미리 정해져 있어서 모든 예산요청을 배정해 주기는 어려울 수도 있습니다. 그래서 정해진 총액 이하에서 가능한 한 최대의 총 예산을 다음과 같은 방법으로 배정합니다.
- 모든 요청이 배정될 수 있는 경우에는 요청한 금액을 그대로 배정합니다.
- 모든 요청이 배정될 수 없는 경우에는 특정한 정수 상한액을 계산하여 그 이상인 예산요청에는 모두 상한액을 배정합니다. 상한액 이하의 예산요청에 대해서는 요청한 금액을 그대로 배정합니다.`
예를 들어, 전체 국가예산이 485이고 4개 지방의 예산요청이 각각 120, 110, 140, 150일 때, 상한액을 127로 잡으면 위의 요청들에 대해서 각각 120, 110, 127, 127을 배정하고 그 합이 484로 가능한 최대가 됩니다.각 지방에서 요청하는 예산이 담긴 배열 budgets과 총 예산 M이 매개변수로 주어질 때, 위의 조건을 모두 만족하는 상한액을 return 하도록 solution 함수를 작성해주세요.
제한 사항
- 지방의 수는 3 이상 100,000 이하인 자연수입니다.
- 각 지방에서 요청하는 예산은 1 이상 100,000 이하인 자연수입니다.
- 총 예산은
지방의 수
이상 1,000,000,000 이하인 자연수입니다.
입출력 예
budgets | M | return |
---|---|---|
[120, 110, 140, 150] | 485 | 127 |
구현 과정
- 최소값은 0으로 선언하고, 예산이 담긴 배열 budgets을 오름차순으로 정렬하여 최대값을 구한다.
- 중간값(mid)을 구하고, 배열 각각의 값이 총 예산(M)을 넘지 않으면 배열 값을 합계에 더하고, M을 넘으면 M값을 합계에 더한다.
- 중간값(mid)을 상한액으로 한 합계가 총 예산(M)을 넘지 않으면, 최소값을 중간값 + 1 높여 확인한다. 이때, 일단은 예산을 넘지 않으므로 중간값을 answer에 저장해둔다. 반대로 합계가 총 예산(M)을 넘으면, 최대값을 중간값 -1 낮춰 확인한다.
- 2-4의 과정은 최소값이 최대값보다 커지기 전까지 반복한다.
구현 코드
import java.util.Arrays;
class Solution {
public int solution(int[] budgets, int M) {
int answer = 0;
int min = 0;
int max = budgets[0]; //최대값 초기값 셋팅
//budgets을 오름차순으로 정렬하고 최대값 구하기
Arrays.sort(budgets);
for (int i = 1; i < budgets.length; i++) {
if (budgets[i] > max) {
max = budgets[i];
}
}
//최소값이 최대값보다 작을 때까지 반복한다.
while (min <= max) {
//중간값 구하기
int mid = (max + min) / 2;
int sum = 0; // 합계 초기화
//중간값으로 한 budgets의 합계 구하기
for (int i = 0; i < budgets.length; i++) {
if (mid < budgets[i]) sum += mid;
else sum += budgets[i];
}
if(sum <= M){ // 총합이 예산 M을 넘지 않는 경우, 최소값을 +1 높여 확인한다.
answer = mid; //조건에 충족하므로 값을 일단 저장함.
min = mid + 1;
} else if(sum > M){ // 총합이 예산 M을 넘는 경우, 최대값을 -1 낮춰 확인한다.
max = mid - 1;
}
}
return answer;
}
}
리팩토링 1
- max 값을 찾는 반복문 대신에 Arrays 클래스의 max 메소드를 이용하여 최대값을 찾는다.
- 속도가 느려서 확인해보니, 오름차순 정렬은 불필요하다. 배열값을 찾는 게 아니라, 합계를 가지고 범위를 좁히기 때문.
- 반복문에서 배열의 크기를 가져오는 대신 for-each문을 사용했다.
import java.util.Arrays;
class Solution {
public int solution(int[] budgets, int M) {
int answer = 0;
int start = 0;
int end = Arrays.stream(budgets).max().orElse(0); //max 값을 찾는 반복문 대신에 Arrays 클래스의 max 메소드를 이용하여 최대값을 찾는다.
int len = budgets.length;
// Arrays.sort(budgets);
//
// for (int i = 1; i < budgets.length; i++) { //정렬은 불필요. 배열값을 찾는 게 아니라, 합계를 가지고 범위를 좁히기 때문.
// if (budgets[i] > max) {
// max = budgets[i];
// }
// }
while (start <= end) {
int mid = (end + start) / 2;
long sum = 0;
for(int budget : budgets) { //반복문에서 배열의 크기를 가져오는 대신 for-each문을 사용함.
if (budget > mid) sum += mid;
else sum += budget;
}
if(sum <= M){
answer = mid;
start = mid + 1;
} else {
end = mid - 1;
}
}
return answer;
}
}
리팩토링 2
- Intstream으로 max값을 구한다.
- 람다식과 stream을 사용하여 for-each문을 개선했다. Math.min() 메서드에서는 가변변수는 사용할 수 없으므로 중간값(mid) 변수를 final 상수로 선언했다.
import java.util.stream.IntStream;
class Solution {
public int solution(int[] budgets, int M) {
int answer = 0;
int start = 0;
int end = IntStream.of(budgets).max().orElse(0); // Intstream으로 max값을 찾을 수 있다.
while (start <= end) {
final int mid = (end + start) / 2; //mid는 가변변수 사용할 수 없으므로 final 상수로 바꿔준다.
int sum = IntStream.of(budgets)
.map(budget -> Math.min(budget, mid)) //budget을 mid와 비교하여 작은 값을 선택한다.
.sum();
if(sum <= M){
answer = mid;
start = mid + 1;
} else {
end = mid - 1;
}
}
return answer;
}
}
이진탐색 알고리즘
- 이진 탐색 : 정렬되어 있는 리스트에서 탐색 범위를 절반씩 좁혀가며 데이터를 탐색하는 방법
- 이진 탐색은 시작점, 끝점, 중간점을 이용하여 탐색 범위를 설정한다.
- 시간 복잡도는 0(logN)을 보장한다.
이진탐색 소스코드 구현
public class Main {
public static void main(String[] args) {
int arr[] = {20, 30, 15, 17};
int target = 15;
int answer = 0;
int start = 0;
int end = arr.length -1;
while (start <= end) {
int mid = (start + end) / 2;
// 찾은 경우 중간점 인덱스 반환
if (arr[mid] == target) answer = mid;
// 중간값의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
else if (arr[mid] > target) end = mid -1;
// 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
else start = mid + 1;
}
System.out.println(answer + 1);
}
}
파라메트릭 서치
- 파라메트릭 서치란 최적화(함수의 값을 최대한 높이거나, 낮춘다.) 문제를 결정 문제로 바꾸어 해결하는 기법입니다.
- 예시 : 특정한 조건을 만족하는 가장 알맞은 값을 빠르게 찾는 최적화 문제
- 일반적으로 코딩 테스트에서 파라메트릭 서치 문제는 이진 탐색을 이용하여 해결할 수 있다.