#2467 용액
난이도 : 골드 5
유형 : 이진탐색 / 투포인터
▸ 문제
KOI 부설 과학연구소에서는 많은 종류의 산성 용액과 알칼리성 용액을 보유하고 있다. 각 용액에는 그 용액의 특성을 나타내는 하나의 정수가 주어져있다. 산성 용액의 특성값은 1부터 1,000,000,000까지의 양의 정수로 나타내고, 알칼리성 용액의 특성값은 -1부터 -1,000,000,000까지의 음의 정수로 나타낸다.
같은 양의 두 용액을 혼합한 용액의 특성값은 혼합에 사용된 각 용액의 특성값의 합으로 정의한다. 이 연구소에서는 같은 양의 두 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들려고 한다.
예를 들어, 주어진 용액들의 특성값이 [-99, -2, -1, 4, 98]인 경우에는 특성값이 -99인 용액과 특성값이 98인 용액을 혼합하면 특성값이 -1인 용액을 만들 수 있고, 이 용액의 특성값이 0에 가장 가까운 용액이다. 참고로, 두 종류의 알칼리성 용액만으로나 혹은 두 종류의 산성 용액만으로 특성값이 0에 가장 가까운 혼합 용액을 만드는 경우도 존재할 수 있다.
산성 용액과 알칼리성 용액의 특성값이 정렬된 순서로 주어졌을 때, 이 중 두 개의 서로 다른 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액을 찾는 프로그램을 작성하시오.
▸ 입력
첫째 줄에는 전체 용액의 수 N이 입력된다. N은 2 이상 100,000 이하의 정수이다. 둘째 줄에는 용액의 특성값을 나타내는 N개의 정수가 빈칸을 사이에 두고 오름차순으로 입력되며, 이 수들은 모두 -1,000,000,000 이상 1,000,000,000 이하이다. N개의 용액들의 특성값은 모두 서로 다르고, 산성 용액만으로나 알칼리성 용액만으로 입력이 주어지는 경우도 있을 수 있다.
▸ 출력
첫째 줄에 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액의 특성값을 출력한다. 출력해야 하는 두 용액은 특성값의 오름차순으로 출력한다. 특성값이 0에 가장 가까운 용액을 만들어내는 경우가 두 개 이상일 경우에는 그 중 아무것이나 하나를 출력한다.
문제 풀이
두 수의 합이 0에 가까운 집합을 구하는 문제로 투 포인터 또는 이진탐색으로 풀이가 가능하다. 직관적인 접근 방법으로는 투포인터가 더 쉽다. 이진탐색은 경험을 위해 추가 풀이를 하였다.
투 포인터
입력 데이터는 오름차순으로 주어진다고 했으므로 따로 정렬을 해주지 않아도 된다. 첫 원소는 가장 작은 값이고, 끝 원소는 가장 큰 값이므로 투 포인터를 사용하여 서로 가운데로 향하여 움직이게 한 다음 가장 0에 가까운 수를 구해주면 된다.
- 0과 n-1을 각 투포인터로 지정한다.
- 투포인터를 사용하여 합이 0에 가까운 구간을 탐색한다.
- min > Math.abs(arr[left]+arr[right]) 합이 0에 가장 가까운 값이 갱신되면 저장해준다.
- sum >=0 합이 0보다 크거나 같다면 더 작아져야 하므로 오른쪽 포인터를 이동한다. right--;
- sum <0 합이 0보다 작다면 더 커져야 하므로 왼쪽 포인터를 이동한다. left--;
이진탐색
투 포인터와 아이디어는 같지만 탐색방식이 다르다. 하나의 원소(arr[i])를 픽한 다음에 나머지 원소 중 현재 원소*-1와 가장 가까운 원소를 이진탐색을 통해 찾아준다. 그래야 0에 가장 가까운 값을 찾을 수 있기 때문이다.
- arr[i] 기준점을 뽑는다.
- [i+1 ~ n-1]구간에 있는 원소 중 arr[i]*-1와 가장 가까운 값을 이진탐색을 통해 탐색한다.
- min > Math.abs(arr[i]+arr[mid]) 0에 가장 가까운 값이 갱신되면 저장해준다.
- arr[mid] >= -arr[i] 기준점*-1보다 크다면 위치를 right = mid -1;로 옮겨준다.
- arr[mid] < -arr[i] 기준점*-1보다 작다면 위치를 left=mid+1;로 옮겨준다.
투 포인터 풀이 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
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());
long[] arr = new long[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for(int i=0; i<n; i++) {
arr[i] = Long.parseLong(st.nextToken());
}
int left =0;
int right =n-1;
int ml =0, mr = 0;
long min = Long.MAX_VALUE;
while(left<right) {
long sum = arr[left]+arr[right];
if(min > Math.abs(sum)) {
min = Math.abs(sum);
ml = left; mr = right;
}
if(sum>=0) {
right--;
}else {
left++;
}
}
System.out.println(arr[ml] +" "+arr[mr]);
}
}
이진탐색 풀이 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int n;
static long[] arr;
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
arr = new long[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for(int i=0; i<n; i++) {
arr[i] = Long.parseLong(st.nextToken());
}
long min = Long.MAX_VALUE;
int ml =0, mr = 0;
for(int i=0; i<n-1; i++) {
int left =i+1;
int right =n-1;
while(left<=right) {
int mid = (left+right)/2;
long sum = Math.abs(arr[i]+arr[mid]);
if(min > sum) {
min = sum;
ml = i; mr = mid;
}
if(arr[mid]>= -arr[i]) {
right = mid-1;
}else{
left = mid+1;
}
}
}
System.out.println(arr[ml]+" "+arr[mr]);
}
}
'Dot Algo∙ DS > PS' 카테고리의 다른 글
[BOJ] 백준 1208번 부분수열의 합 2 (Java) (0) | 2021.11.14 |
---|---|
[BOJ] 백준 2143번 두 배열의 합 (Java) (0) | 2021.11.13 |
[BOJ] 백준 3020번 개똥벌레 (Java) (0) | 2021.11.11 |
[BOJ] 백준 2352번 반도체 설계 (Java) (0) | 2021.11.10 |
[BOJ] 백준 7453번 합이 0인 네 정수 (Java) (0) | 2021.11.09 |