이분 탐색은 배열에서 탐색 범위를 절반으로 줄여나가며 원하는 타겟을 효율적으로 찾는 알고리즘이다.
보통 시작 인덱스와 끝 인덱스 범위를 미리 설정해두고, 가운데 인덱스를 구해 타겟보다 큰 지/작은 지에 따라 탐색 범위를 조정해나간다.
대소 비교로 범위를 정하기 때문에 기본적으로 데이터가 정렬되어 있어야 한다.
일반적으로 배열을 모두 순회하며 값을 찾으려면 O(N)의 시간복잡도를 가지지만, 이분 탐색은 탐색 범위가 절반씩 줄어드므로 O(logN)의 시간복잡도를 가진다.
단조성(일방향적인 변화)을 띌 때하나만 존재하는 케이스를 말한다.NO NO NO | YES YES YES
YES YES YES | NO NO NO
가운데 값 === 타겟 값이면 위치 반환가운데 값 > 타겟 값이면 더 높은 범위에서 값을 찾기 위해 시작 인덱스 = 중간 인덱스 + 1로 설정하고 재탐색가운데 값 < 타겟 값이면 더 낮은 범위에서 값을 찾기 위해 끝 인덱스 = 중간 인덱스 - 1로 설정하고 재탐색반복문/재귀로 구현하는 두 가지 방법이 있지만 재귀는 함수 호출이 누적되어 스택 오버플로우가 발생할 수 있으므로 반복문으로 구현하는 것이 더 효율적이다.
function BinarySearch(arr, target){
let start = 0;
let end = arr.length - 1;
let mid;
while(start <= end){
mid = Math.floor((start + end) / 2);
if(arr[mid] === target) return mid;
else if(arr[mid] < target) start = mid + 1;
else if(arr[mid] > target) end = mid - 1;
}
return -1;
}
이분 탐색을 어떻게 써야 할 지 감이 안 잡혀서 힌트를 봤다.
공유기 사이 거리가 최대가 되도록 하는 거리 값을 구해야 하므로 거리를 이분 탐색으로 구해야 한다.
공유기를 어떻게 배치하지? (x) -> 특정 거리에서 공유기 배치가 가능한가? (o)
const fs = require('fs');
const input = fs.readFileSync(process.platform === 'linux' ? '/dev/stdin' : './test.txt').toString().trim().split('\n');
const [N, C] = input.shift().split(' ').map(Number)
const houseArr = input.map(Number).sort((a,b) => a-b);
let answer = 0;
function BinarySearch(arr){
let start = 1;
let end = arr[arr.length - 1] - arr[0];
let mid;
while(start <= end){
mid = Math.floor((start + end) / 2);
let cnt = 1;
let last = 0;
for(let i=1;i<N;i++){
if(arr[i] - arr[last] >= mid){
cnt++
last = i
}
}
if(cnt >= C){
answer = mid;
start = mid + 1;
}else{
end = mid - 1;
}
}
console.log(answer)
}
BinarySearch(houseArr)
위의 문제와 유사한 문제로, 블루레이의 최소 크기와 최대 크기를 정해두고 중간값을 이용해 M개 이상 볼 수 있는지에 따라 블루레이 크기를 조정해주면 된다.
처음 풀이에서는 두 가지 원인으로 틀렸다.
주어진 순서대로 녹화해야 한다고 했으므로 순서를 바꾸면 안 된다.const fs = require('fs');
const input = fs.readFileSync(process.platform === 'linux' ? '/dev/stdin' : './test.txt').toString().trim().split('\n');
const [N,M]= input[0].split(' ').map(Number);
const times = input[1].split(' ').map(Number); // 강의 순서가 바뀌면 안 되므로 정렬하면 안 됨!
let answer = 0;
function BinarySearch(arr){
let start = Math.max(...arr); // M이 N개일 때 : 가장 큰 시간
let end = arr.reduce((acc,cur) => acc+cur); // M이 1개일 때 : 모든 시간 합
let mid;
while(start <= end){
mid = Math.floor((start + end) / 2);
let cnt = 1;
let sum = 0;
for(let i=0;i<N;i++){
if(sum + arr[i] <= mid){
sum += arr[i]
}else{
sum = arr[i]
cnt++
}
}
if(cnt <= M){
answer = mid;
end = mid - 1;
}else{
start = mid + 1;
}
}
console.log(answer)
}
BinarySearch(times);
게임을 몇 번 이겨야 승률이 바뀌는지 반환하면 된다.
앞으로 게임은 항상 이기므로 승률은 그대로거나 증가하는 경우밖에 없다.
따라서 승률을 조정하며 승률이 커지면 횟수를 더 줄여보고, 승률이 그대로면 횟수를 더 증가시키며 답을 찾으면 된다.
처음 문제를 풀 때는 end 범위를 잘못 정해서 틀렸다.
가능한 게임 상한선이 사실상 정해져 있지 않아서 가능한 큰 값(10억)으로 설정해야 한다.
const fs = require('fs');
const input = fs.readFileSync(process.platform === 'linux' ? '/dev/stdin' : './test.txt').toString().trim().split('\n');
const [X,Y]= input[0].split(' ').map(Number);
const winningRate = Math.floor(Y * 100 / X);
let answer = -1;
function BinarySearch(){
let start = 1;
let end = 1000000000;
while(start <= end){
let mid = Math.floor((start + end) / 2);
if(Math.floor((100 * (Y + mid)) / (X + mid)) > winningRate){
answer = mid;
end = mid - 1;
}else{
start = mid + 1;
}
}
console.log(answer);
}
BinarySearch();
공통적으로 시작/끝 범위 설정 때문에 계속 틀려서 범위를 잘 정하는게 가장 중요할 것 같다.
값이 중복되는 경우도 있을 수 있어서 단순히 mid 인덱스를 바로 반환하면 틀리게 된다.
const fs = require('fs');
const input = fs.readFileSync(process.platform === 'linux' ? '/dev/stdin' : './test.txt').toString().trim().split('\n');
let T = +input[0];
let idx = 1;
const answer = [];
while(T > 0){
const [N, M] = input[idx++].split(' ').map(Number);
const A = input[idx++].split(' ').map(Number);
const B = input[idx++].split(' ').map(Number).sort((a,b) => a-b);
let cnt = 0;
for(let i=0;i<A.length;i++){
const v = BinarySearch(A[i],B);
cnt += v;
}
answer.push(cnt)
T--;
}
function BinarySearch(target, arr){
let start = 0;
let end = arr.length;
let mid;
while(start <= end){
mid = Math.floor((start + end) / 2);
if(arr[mid] === target){
break;
}else if(arr[mid] < target){
start = mid + 1;
}else{
end = mid - 1;
}
}
return mid;
}
console.log(answer.join('\n'));
타겟이 같거나 작을 때는 end를 mid로 설정해서 범위를 넘지 않게 하면 start가 가능한 개수가 된다.
const fs = require('fs');
const input = fs.readFileSync(process.platform === 'linux' ? '/dev/stdin' : './test.txt').toString().trim().split('\n');
let T = +input[0];
let idx = 1;
const answer = [];
while(T > 0){
const [N, M] = input[idx++].split(' ').map(Number);
const A = input[idx++].split(' ').map(Number);
const B = input[idx++].split(' ').map(Number).sort((a,b) => a-b);
let cnt = 0;
for(let i=0;i<A.length;i++){
const v = BinarySearch(A[i],B);
cnt += v;
}
answer.push(cnt)
T--;
}
function BinarySearch(target, arr){
let start = 0;
let end = arr.length;
while(start < end){
let mid = Math.floor((start + end) / 2);
if(arr[mid] < target){
start = mid + 1;
}else{
end = mid;
}
}
return start;
}
console.log(answer.join('\n'));