파이썬 알고리즘 인터뷰 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 행렬에서 값을 찾아내는 효율적인 알고리즘을 구현하라. 행렬은 왼쪽에서 오른쪽, 위에서 아래 오름차순으로 정렬되어 있다
첫 행의 맨 뒤에서 탐색
| 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)
 | 
- 행렬에 값이 존재하는지 여부를 위에서부터 차례로 한 줄씩 탐색한다