이진 검색 ) 10억번의 시행 횟수를 단 30번의 시행으로
코딩테스트 문제에서 주어진 데이터들을 순회할 때, 답을 찾기 위해 직접적으로 순회하면 시간 조건을 초과하는 경우가 있습니다. 이 때 시간 복잡도를 줄이기 위해 투 포인터와 이진 검색을 고려합니다. 두 개의 포인터로 정답 도출이 가능할 것 같을 때에 투 포인터 알고리즘을 적용하고, 불가능 할 때에는 이진 검색을 적용합니다.
▪︎ 이진 검색
이진 검색은 오름차순으로 정렬된 리스트에서 특정한 값의 위치를 찾는 알고리즘입니다. 탐색 범위를 절반씩 줄여나가면서 값을 찾기 때문에 빠른 속도를 보장합니다.
위 배열에서 121이라는 숫자가 몇 번째 index에 있는지를 도출해야한다고 생각해봅시다. index 0부터 순회하며 121을 찾을 수 있지만 배열의 범위를 보면 121이 아닌 더 큰 값이 입력으로 들어왔을 경우 최악의 경우 10^9 번의 동작을 순회해야 합니다. 완전 탐색 포스팅에서 확인했듯이 코딩 테스트에서 우리는 10^8 안으로 시행 횟수를 기준으로 생각하기 때문에 이 방법은 불가능 합니다.
그러면 이진 검색을 활용하면 어떨까요? 최악의 경우에도 30번의 시행만으로 답을 찾아낼 수 있습니다.
▫︎ 이진 검색 구현
- 인덱스의 최소값 / 최대값을 변수로 선언합니다.
- 범위 내 중간값을 지닌 인덱스에 위치한 요소를 판단하여 121보다 크다면 최대값을 줄여 범위를 좁히고, 작다면 최소값을 높여 범위를 좁힙니다.
- 121을 도출할 때까지 2번의 과정을 반복합니다.
1 | let start = 0; |
기본적인 구현 방법은 위와 같고, 도출할 값의 특성에 따라 조금씩 바뀔 수 있습니다. 예를 들어 어떠한 조건에 맞는 최소값을 찾아야 한다면 도출되는 값은 여러개가 될 것이므로 계속 해서 답을 업데이트해야 합니다. 이 때 어떠한 조건문에 답을 업데이트 하는 로직을 추가할지 잘 생각해야 합니다. 위 이진 검색의 구현 코드는 ‘기본적으로 이렇게 작성된다’라고 생각하며 넘어가고 실제 문제 풀이를 통해 어떤식으로 코드가 작성되고 진행되는지 살펴보겠습니다.
▫︎ 최소 / 최대값 도출하기
때때로 코딩테스트에서 범위에 속하는 값들 중 최소 / 최대값을 찾아야하는 경우도 있습니다. 이 경우 위 코드를 조금 변형하여 풀어낼 수 있습니다. 이 때 answer에 mid값을 할당하는 코드의 위치가 중요합니다. 최소값을 구할 때는 end값을 조정할 때 answer값을 재할당 해야하고, 최대값을 구할 때는 start값을 조정할 때 answer를 재할당 해야합니다. 밑에 작성할 문제 풀이를 통해 확인해봅시다.
▪︎ Example of Apply
모든 문제에서 처음 완전 탐색이 가능한지부터 생각합니다. 위 문제에서는 N 과 M에 따라 데이터를 몇번이나 순회할지 결정되어집니다. 입력 설명을 볼 때 N의 범위가 1000보다 작습니다. M에 따라 최악의 경우 1000개를 순회하면서 각 요소들을 어떤 DVD에 넣을지를 분기한다면, 시행 횟수는 2^8을 가볍게 넘을 것입니다. 따라서 이진 검색을 통해 나올 수 있는 정답의 범위를 정하고 조건에 부합하는 값 중 최소값을 찾아보겠습니다.
▫︎ 문제 접근
- 한 노래를 쪼개서 두 개의 DVD에 녹화할 수 없기 때문에, 용량 크기는 최소한 주어진 배열의 요소 중 최대값보다는 커야한다.
- 모두 한 DVD에 넣는 경우보다 용량이 커질 경우는 없다. 따라서 곡의 길이를 전부 합한 값이 최대값이다.
- 이진 검색을 통해 조건을 판별한다.
1 | function solution(m, arr) { |