상당히 유익한 글이고 이분 탐색 문제 풀이에서 발생하는 오답을 잡아주기 때문에 저와 비슷한 어려움을 겪으시는 분들은 꼭 읽어보시길 추천드립니다.
구현은 쉽지만 오류 찾기가 어려운 이분 탐색 문제였습니다. 감사합니다!
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static StringTokenizer st;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer(br.readLine());
long N = Integer.parseInt(st.nextToken());
long M = Integer.parseInt(st.nextToken());
long[] staffs = new long[(int) N];
st = new StringTokenizer(br.readLine());
long min = Integer.MAX_VALUE;
for (int n = 0; n < N; n++) {
staffs[n] = Integer.parseInt(st.nextToken());
min = Math.min(min, staffs[n]);
}
long left = 0;
long right = min * M;
while(left+1 < right) {
long center = (left + right)/2;
long balloon = 0;
for (int n = 0; n < N; n++) {
balloon += (center / staffs[n]);
}
if(balloon >= M) {
right = center;
}else {
left = center;
}
}
System.out.println(right);
}
}
또 다른 웰 노운(Well Known인데 우리는 모르는) 알고리즘, 분리집합의 등장입니다.
유니온이라고도 알려져 있습니다.
유니온은 일종의 단방향 트리라고 생각하셔도 될 것 같습니다.
데이터들을 집합들로 구분할때 어떻게 할 수 있을까요?
일일히 넘버링을 붙이거나 이름을 붙여도 되겠지만, 구현은 어떻게 하실건가요?
두개의 집합이 합쳐질때는요?
분리집합은 자신들을 대표하는 데이터를 부모로 가리킴으로서 자신이 속한 집합을 구분합니다.
이 데이터를 노드라고 생각한다면, 집합은 여러 노드들이 연결된 그래프가 되겠죠.
자신이 이미 속한 집합의 노드와 연결된다면 그 그래프는 사이클이 생긴 그래프가 됩니다.
간단하죠?
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static StringTokenizer st;
static int[] parent;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int answer = 0;
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
parent = new int[N];
for (int n = 0; n < N; n++) {
parent[n] = n;
}
for (int m = 1; m <= M; m++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int aParent = findParent(a);
int bParent = findParent(b);
if (aParent == bParent) {
answer = m;
break;
} else {
parent[aParent] = bParent;
}
}
System.out.println(answer);
}
static int findParent(int a) {
if (parent[a] == a) {
return a;
} else {
parent[a] = findParent(parent[a]);
return parent[a];
}
}
}
기본적인 투 포인터가 데이터의 양 쪽 끝에서 좁혀 들어오면서 탐색한다면, 슬라이딩 윈도는 한쪽 끝에서 시작해서 연속된 특정 범위를 찾습니다.
조금 더 상세히 설명해 볼까요?
N길이의 수열에서 연속된 부분을 구한다는 것은, 연속된 부분합의 시작점과 끝점을 정한다는 뜻입니다.
즉 N*N개의 경우가 존재한다는 뜻입니다.
슬라이딩 윈도우는 이 시작점과 끝점을 모든 경우에 대해 계산해보지 않고, 현재 부분에서 조건보다 작으면 연속된 부분을 늘리고 크면 연속된 부분을 줄이면서 진행합니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static StringTokenizer st;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int S = Integer.parseInt(st.nextToken());
int[] num = new int[N];
st = new StringTokenizer(br.readLine());
for (int n = 0; n < N; n++) {
num[n] = Integer.parseInt(st.nextToken());
}
int left = 0;
int right = 1;
int sum = num[0];
int answer = 100_001;
while (left < N) {
if (sum >= S) {
answer = Math.min(answer, right - left);
sum -= num[left];
left++;
} else {
if (right == N) {
break;
} else {
sum += num[right];
right++;
}
}
}
if(answer == 100_001) {
System.out.println(0);
}else {
System.out.println(answer);
}
}
}
투포인터 알고리즘을 통해 절댓값이 0에 가장 가까운 두개의 특성값을 기록하고 출력하면 간단히 풀립니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static StringTokenizer st;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
int[] num = new int[N];
int value = Integer.MAX_VALUE;
int[] answer = new int[2];
st = new StringTokenizer(br.readLine());
for (int n = 0; n < N; n++) {
num[n] = Integer.parseInt(st.nextToken());
}
int left = 0;
int right = N - 1;
while (left < right) {
int sum = num[left] + num[right];
int abs = Math.abs(sum);
if (abs < value) {
value = abs;
answer[0] = num[left];
answer[1] = num[right];
}
if (sum <= 0) {
left++;
} else {
right--;
}
}
System.out.println(answer[0] + " " + answer[1]);
}
}