배열이 크기순으로 정렬되어 있을때 O(logN) 시간에 값의 위치를 탐색할 수 있는 알고리즘. 배열이 크기순으로 정렬되어 있기에 구역을 절반씩 나눠 원하는 값이 어떤 영역에 있는지 탐색을 반복해 위치를 특정한다.
logN의 시간복잡도가 얼마나 강력한지 설명하자면 N이 long타입의 최고 범위 10^18이라고 가정하더라도, O(log10^18) = O(18*log10) = 약 54. 무려 상수에 가깝게 줄어든다. 즉 억을 넘어 경단위의 문제도 풀이 가능하다.
이분탐색 알고리즘은 크게 2가지 목적으로 사용할 수 있다.
1. 정렬된 배열에서 특정 값 인덱스 탐색
2. 정렬된 배열에 값을 들어갈 인덱스 탐색
Lower bound: target 이상의 값이 나오는 처음 인덱스 반환
def lower_bound(arr, target):
l = 0
r = len(arr) # [l, r)
while l < r:
mid = l + (r - l) // 2
if target <= arr[mid]: # 핵심 차이점
r = mid # 열림을 기준으로 하기에 넘어가지 않도록
else:
l = mid + 1
return l
Upper bound: target 초과 값이 나오는 처음 인덱스 반환 (Lower에서 등호하나만 빼서 구현했다.)
def upper_bound(arr, target):
l = 0
r = len(arr) # [l, r)
while l < r:
mid = l + (r - l) // 2
if target < arr[mid]:
r = mid
else:
l = mid + 1
return l
첫번째 true찾기 (false는 당연히 not연산 한번이면 동일하다)
def parametric(l, r, ok):
# [l, r)
while l < r:
mid = l + (r - l) // 2
if ok(mid):
r = mid
else:
l = mid + 1
return l
mid = left + (right - left) / 2
// 1을 찾는 문제인 경우
{0} // 정답이 없는 경우가 있다면 이것도 확인해줘야 한다.
{1} // 정답을 제대로 출력하는지 확인 + 경계값 첫번째 원소인 경우
{0, 1} // 경계값 마지막 원소인 경우
{0, 1, 1, 2} // upper, lower가 제대로 적용되었나 확인