[Leetcode] 268. Missing Number
Problem
nums 숫자 배열이 존재하고, 0~n까지의 숫자를 오직 한개씩 포함하고 있다. 배열에서 빠진 숫자를 구하여라.
만약 빠진 숫자가 없다면 마지막 값이 빠진것을 고려하자.
Solution
반복문, Binary Search
이 문제를 해결하는 방법은 두가지가 존재한다.
nums를 오름차순으로 정렬한다음 반복문을 이용해서 배열의 순서대로 인덱스(i)와 값(nums[i])을 비교하는 방법과 이진탐색을 이용해서 인덱스(i)와 값(nums[i])을 비교하는 방법
두 방법에서 답을 조건은 같지만 인덱스를 조건하는 방식은 다르다.
| 실행 방법 | 빅오 | |
|---|---|---|
| 반복문 | 인덱스 순서대로 | O(n) |
| 이진탐색 | 찾을 범위를 줄임 | O(log n) |
코드를 구현해보고 두 로직의 속도를 비교해보자.
반복문 코드
nums을 정렬해준다.- 정렬한다면
nums의 값은 해당 인덱스와 값이 같아야한다. 만약 다르다면 이전 인덱스에서 어떤 값이 빠진것이다. - 인덱스와 값을 비교하면서 값과 틀린 곳이 있다면 해당 인덱스를 리턴하고, 빠진 값이 없다면 마지막 요소가 빠진것으로 생각하고 마지막 값을 리턴한다.
ts
function forLoop(nums: number[]): number {
nums.sort((a, b) => a - b)
for (let i = 0; i < nums.length; i++) {
if (i !== nums[i]) return i
}
return nums.length
}이진탐색
- 이진탐색 실행 조건의
nums배열을 오름차순으로 정렬해준다. (값과 해당 인덱스가 같은 배열이 완성) - left, right를 배열의 첫번째, 마지막 인덱스로 설정해준다. left와 right는 탐색 범위이다.
- 반복문을 이용해서 범위가 **하나의 요소로 좁혀질때까지의 조건(타겟)**을 추가해준다.
- 범위를 반으로 줄여가면서 탐색하는것이 이진 탐색의 원리이다. 이 원리를 이용해서 양쪽 인덱스를 중간 인덱스를 찾는다.
- 중간값(mid)과 중간값의 값(nums[mid])가 같은지 확인한다.
- 만약 같다면 : 이전 인덱스에서 빠진 값이 없다는 것을 의미한다.
- 틀리다면 : 이전에 어떤 값이 빠진 것을 의미
- 같을 경우 left의 범위를 변경해준다. (mid + 1), 틀린경우 이전 인덱스에서 타겟을 찾아야하기 때문에 right 범위를 변경해준다 (mid -1)
mid의 + 1을 해준 이유 : mid는 이미 확인한 값이기 때문에 다음 요소부터 확인. 그러면 left는 인덱스와 값이 틀린값을 최종적으로 가르킨다.mid의 - 1을 해준 이유 : 위와 같인 이유(mid는 이미 확인한 값이기 때문에 이전 요소부터 확인)이면서 내가 찾는 타겟일 수 있다.
- left와 right는 하나의 값을 가르키게된다. 이것이 최초로 인덱스와 값이 틀린 요소입니다.
ts
function binarySearch(nums: number[]): number {
nums.sort((a, b) => a - b)
let [left, right] = [0, nums.length - 1]
while (left < right) {
const mid = Math.floor((left + right) / 2)
if (mid === nums[mid]) left = mid + 1
else right = mid - 1
}
return left === nums[left] ? left + 1 : left
}비교
배열의 사이즈를 100,000,000로 늘리고 마지막 인덱스 근방을 뺀 input을 만듭니다. 이 케이스는 순서대로 탐색했을 때 최악의 케이스를 만들었습니다. 그런다음 실행 시간을 출력하는 함수를 생성하여 실행했을때 입니다.
ts
function play () {
const size = 100_000_000
const input = Array.from(Array(size), (_,i) => i)
input.splice(size-2,1)
const timer = (fn: Function): void => {
console.time(fn.name)
console.log(`${fn.name} :: ${fn()}`)
console.timeEnd(fn.name)
}
timer(binarySearch.bind(binarySearch, input))
timer(forLoop.bind(forLoop, input))
}bash
bound binarySearch :: 99999998
bound binarySearch: 1.427ms
bound forLoop :: 99999998
bound forLoop: 141.067ms속도를 보면 이진탐색이 매우 빠른것을 확인 할 수 있습니다.
아래는 빅오계산법으로 속도 향상 폭을 보여주는 그래프입니다.
![]()