Home 이진 탐색 (Python Algorithm interview 18장)
Post
Cancel

이진 탐색 (Python Algorithm interview 18장)

파이썬 알고리즘 인터뷰 18장을 정리한 내용입니다.

이진 검색

Binary Search(이진 검색)이란 정렬된 배열에서 타겟을 찾는 검색 알고리즘이다.

  • 이진 검색은 값을 찾아내는 시간 복잡도가 $O(\log n)$ 이라는 점에서 대표적인 로그 시간 알고리즘이며, 이진 탐색 트리와 유사한 점이 많다
  • 이진 탐색 트리가 정렬된 구조를 저장하고 탐색하는 “자료구조” 라면 이진 검색은 정렬된 배열에서 값을 찾아내는 “알고리즘” 자체를 말한다
1
2
3
4
import math
math.log2(100000000)

'''26.575424...'''
  • 1억개의 아이템도 27번이면 모두 찾아낼 수 있다

이진 검색 예시

1
array = [2, 4, 9, 15, 26, 28, 31]
  • 이진 검색을 이용해서 9 를 찾아볼 것이다
  • 배열은 오름차순으로 정렬되어 있다.

Step 1

  • 임의의 값인 15 를 선택한다
  • 찾을 값과 선택한 값을 비교한다 (9 < 15)
  • 찾을 값은 15 를 기준으로 왼쪽에 있다는 것을 알 수 있다

Step 2

  • 15 를 기준으로 왼쪽에 있는 배열을 다시 탐색한다
  • 임의의 값인 4 를 선택한다
  • 찾을 값과 선택한 값을 비교한다 (4 < 9)
  • 찾을 값은 선택한 값 오른쪽에 위치한다

Step 3

  • 4 를 기준으로 오른쪽에 있는 배열을 정렬한다
  • 찾을 값 9 만 존재하며 원하는 값을 찾았다

이진 검색 종료


#65 이진 검색

정렬된 nums를 받아 이진 검색으로 target에 해당하는 인덱스를 찾아라

입력

1
2
3
4
nums = [-1, 0, 3, 5, 9, 12]
target = 9

# answer = 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def search(nums: list, target: int) -> int:
    def binary_search(left, right):
        if left <= right:
            mid = (left + right) // 2

            if nums[mid] < target:
                return binary_search(mid + 1, right)
            elif nums[mid] > target:
                return binary_search(left, mid - 1)
            else:
                return mid
        else:
            return -1
    return binary_search(0, len(nums) - 1)
  • 재귀로 이진 검색을 구현한 것이다
  • 절반씩 범위를 줄여나가면서 맞을 때까지 재귀 호출을 한다

반복 풀이

1
2
3
4
5
6
7
8
9
10
11
12
def BS(nums: list, target: int) -> int:
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2

        if nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
        else:
            return mid
    return -1

이진 검색 모듈

1
2
3
4
5
6
7
def moduleSearch(nums: list, target: int) -> int:
    index = bisect.bisect_left(nums, target)

    if index < len(nums) and nums[index] == target:
        return index
    else:
        return -1
  • 파이썬에 이진 검색 모듈을 이용해서 풀 수 있다
  • bisect 모듈은 이진 검색 알고리즘이다

이진 검색을 사용하지 않는 index 풀이

1
2
3
4
5
def NotIndex(nums: list, target: int) -> int:
    try:
        return nums.index(target)
    except ValueError:
        return -1
  • 파이썬에서 제공하는 해당 값의 인덱스를 찾아내는 index() 메소드를 활용한다
  • 존재하지 않는 값이면 에러가 발생하기 때문에 예외처리를 해준다
  • 이 코드는 이진 검색이 아니며 이진 검색을 요구하는 문제에는 사용하면 안된다

#66 회전 정렬된 배열 검색

특정 피벗을 기준으로 회전하여 정렬된 배열에서 target 값의 인덱스를 출력하라

입력

1
2
nums = [4, 5, 6, 7, 0, 1, 2]
target = 1

피벗을 기준으로 한 이진 검색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def search(nums: list, target: int) -> int:
    if not nums:
        return -1

    # 최솟값을 찾기
    left, right = 0, len(nums) - 1
    while left < right:
        mid = left + (right - left) // 2

        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    pivot = left

    # 피벗 기준 이진 검색
    left, right = 0, len(nums)
    while left <= right:
        mid = left + (right - left) // 2
        mid_pivot = (mid + pivot) % len(nums)

        if nums[mid_pivot] < target:
            left = mid + 1
        elif nums[mid_pivot] > target:
            right = mid - 1
        else:
            return mid_pivot

    return -1

#67 두 배열의 교집합

두 배열의 교집합을 구하라

입력

1
2
num1 = [1, 2, 2, 1]
num2 = [2, 2]

이진 검색으로 일치 여부 판별

1
2
3
4
5
6
7
8
9
def intersection(num1: list, num2: list) -> set:
    res = set()
    num2.sort()
    for n1 in num1:
        #  이진 검색으로 일치 여부 판별
        i2 = bisect.bisect_left(num2, n1)
        if len(num2) > 0 and len(num2) > i2 and n1 == num2[i2]:
            res.add(n1)
    return res
  • 한쪽은 순서대로 탐색, 다른쪽은 정렬해서 이진 검색으로 값을 찾는다
  • num2 를 정렬한 상태에서 num1 을 순차 반복하면서 num2 를 이진 검색 한다

투 포인터로 일치 여부 판별

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def intersectioin_two_pointer(num1: list, num2: list) -> set:
    res = set()
    num1.sort()
    num2.sort()
    i = j = 0
    # 투 포인터 우측으로 이동하며 일치 여부 판별
    while i < len(num1) and j < len(num2):
        if num1[i] > num2[j]:
            j += 1
        elif num1[i] < num2[j]:
            i += 1
        else:
            res.add(num1[i])
            i += 1
            j += 1
    return res
  • 각각의 입력받은 리스트를 정렬한다
  • 값이 작은 쪽 배열 포인터가 한 칸씩 앞으로 이동하는 형태이고 어느 한쪽의 포인터가 끝까지 도달하면 종료하게된다

#68 두 수의 합

정렬된 배열을 받아 덧셈하여 타겟을 만들 수 있는 배열의 두 숫자 인덱스를 리턴하라 (배열의 시작은 1로 한다)

입력

1
2
numbers = [2, 7, 11, 15]
target = 9

투 포인터

1
2
3
4
5
6
7
8
9
def twoPointer(number: list, target: int) -> list:
    left, right = 0, len(number) - 1
    while not left == right:
        if number[left] + number[right] < target:
            left += 1
        elif number[left] + number[right] > target:
            right -= 1
        else:
            return [left + 1, right + 1]

bisect 모듈

1
2
3
4
5
6
7
def bisect_slicing(number: list, target: int) -> list:
    for k, v in enumerate(number):
        expected = target - v
        nums = number[k + 1]
        i = bisect.bisect_left(nums, expected)
        if i < len(nums) and number[i + k + 1] == expected:
            return [k + 1, i + k + 2]

#69 2D 행렬 검색

mxn 행렬에서 값을 찾아내는 효율적인 알고리즘을 구현하라. 행렬은 왼쪽에서 오른쪽, 위에서 아래 오름차순으로 정렬되어 있다

/assets/img/posts/pyAlgo/chapter18/pic.png

첫 행의 맨 뒤에서 탐색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def searcgMatrix(matrix: list, target: int) -> bool:
    if not matrix:
        return False

    # 첫 행의 맨 뒤
    row = 0
    col = len(matrix[0]) - 1

    while row <= len(matrix) - 1 and col >= 0:
        if target == matrix[row][col]:
            return True
        elif target < matrix[row][col]:
            col -= 1
        elif target > matrix[row][col]:
            row += 1
    return False
  • 첫 행의 맨 뒤 요소를 선택하고 타겟이 더 작으면 왼쪽으로, 크면 아래로 이동한다

파이썬 다운 방식

1
2
def searchMat(matrix, target):
    return any(target in row for row in matrix)
  • 행렬에 값이 존재하는지 여부를 위에서부터 차례로 한 줄씩 탐색한다
This post is licensed under CC BY 4.0 by the author.

병합 정렬, 삽입 정렬 (Python Algorithm interview 17장)

비트 조작, 해밍 거리 (Python Algorithm interview 19장)