Skip to content

Latest commit

 

History

History
471 lines (359 loc) · 12.8 KB

chapter17.rst

File metadata and controls

471 lines (359 loc) · 12.8 KB

chapter 17: search

17.1 binary search

# # Binary search works for a sorted array. # Note: The code logic is written for an array sorted in # increasing order. # T(n): O(log n) #

def binary_search(array, query):
    lo, hi = 0, len(array) - 1
    while lo <= hi:
        mid = (hi + lo) // 2
        val = array[mid]
        if val == query:
            return mid
        elif val < query:
            lo = mid + 1
        else:
            hi = mid - 1
    return None

def binary_search_recur(array, low, high, val):
    if low > high:       # error case
        return -1
    mid = (low + high) // 2
    if val < array[mid]:
        return binary_search_recur(array, low, mid - 1, val)
    elif val > array[mid]:
        return binary_search_recur(array, mid + 1, high, val)
    else:
        return mid

17.2 find min rotate

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

Find the minimum element. The complexity must be O(logN)

You may assume no duplicate exists in the array.

def find_min_rotate(array):
    low = 0
    high = len(array) - 1
    while low < high:
        mid = (low + high) // 2
        if array[mid] > array[high]:
            low = mid + 1
        else:
            high = mid

    return array[low]

def find_min_rotate_recur(array, low, high):
    mid = (low + high) // 2
    if mid == low:
        return array[low]
    elif array[mid] > array[high]:
        return find_min_rotate_recur(array, mid + 1, high)
    else:
        return find_min_rotate_recur(array, low, mid)

17.3 firt occurrence

# # Find first occurance of a number in a sorted array (increasing order) # Approach- Binary Search # T(n)- O(log n) #

def first_occurrence(array, query):
    lo, hi = 0, len(array) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        #print("lo: ", lo, " hi: ", hi, " mid: ", mid)
        if lo == hi:
            break
        if array[mid] < query:
            lo = mid + 1
        else:
            hi = mid
    if array[lo] == query:
      return lo

17.4 jump search

import math

def jump_search(arr,target):
    """Jump Search
        Worst-case Complexity: O(√n) (root(n))
        All items in list must be sorted like binary search

        Find block that contains target value and search it linearly in that block
        It returns a first target value in array

        reference: https://en.wikipedia.org/wiki/Jump_search

    """
    n = len(arr)
    block_size = int(math.sqrt(n))
    block_prev = 0
    block= block_size

    # return -1 means that array doesn't contain taget value
    # find block that contains target value

    if arr[n - 1] < target:
        return -1
    while block <= n and arr[block - 1] < target:
        block_prev = block
        block += block_size

    # find target value in block

    while arr[block_prev] < target :
        block_prev += 1
        if block_prev == min(block, n) :
            return -1

    # if there is target value in array, return it

    if arr[block_prev] == target :
        return block_prev
    else :
        return -1

17.5 last occurrence

# # Find last occurance of a number in a sorted array (increasing order) # Approach- Binary Search # T(n)- O(log n) #

def last_occurrence(array, query):
    lo, hi = 0, len(array) - 1
    while lo <= hi:
        mid = (hi + lo) // 2
        if (array[mid] == query and mid == len(array)-1) or \
           (array[mid] == query and array[mid+1] > query):
            return mid
        elif (array[mid] <= query):
            lo = mid + 1
        else:
            hi = mid - 1

17.6 linear search

# # Linear search works in any array. # # T(n): O(n) #

def linear_search(array, query):
    for i in range(len(array)):
        if array[i] == query:
            return i

    return -1

17.7 next greatest letter

Given a list of sorted characters letters containing only lowercase letters, and given a target letter target, find the smallest element in the list that is larger than the given target.

Letters also wrap around. For example, if the target is target = 'z' and letters = ['a', 'b'], the answer is 'a'.

Input: letters = ["c", "f", "j"] target = "a" Output: "c"

Input: letters = ["c", "f", "j"] target = "c" Output: "f"

Input: letters = ["c", "f", "j"] target = "d" Output: "f"

Reference: https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/

import bisect

"""
Using bisect libarary
"""
def next_greatest_letter(letters, target):
    index = bisect.bisect(letters, target)
    return letters[index % len(letters)]

"""
Using binary search: complexity O(logN)
"""
def next_greatest_letter_v1(letters, target):
    if letters[0] > target:
        return letters[0]
    if letters[len(letters) - 1] <= target:
        return letters[0]
    left, right = 0, len(letters) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if  letters[mid] > target:
            right = mid - 1
        else:
            left = mid + 1
    return letters[left]

"""
Brute force: complexity O(N)
"""
def next_greatest_letter_v2(letters, target):
    for index in letters:
        if index > target:
            return index
    return letters[0]

17.8 search insert

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

For example: [1,3,5,6], 5 -> 2 [1,3,5,6], 2 -> 1 [1,3,5,6], 7 -> 4 [1,3,5,6], 0 -> 0

def search_insert(array, val):
    low = 0
    high = len(array) - 1
    while low <=  high:
        mid = low + (high - low) // 2
        if val > array[mid]:
            low = mid + 1
        else:
            high = mid - 1
    return low

17.9 search range

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value. If the target is not found in the array, return [-1, -1].

For example: Input: nums = [5,7,7,8,8,8,10], target = 8 Output: [3,5] Input: nums = [5,7,7,8,8,8,10], target = 11 Output: [-1,-1]

def search_range(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: List[int]
    """
    low = 0
    high = len(nums) - 1
    while low <= high:
        mid = low + (high - low) // 2
        if target < nums[mid]:
            high = mid - 1
        elif target > nums[mid]:
            low = mid + 1
        else:
            break

    for j in range(len(nums) - 1, -1, -1):
        if nums[j] == target:
            return [mid, j]

    return [-1, -1]

17.10 search rotate

Search in Rotated Sorted Array Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

Your algorithm's runtime complexity must be in the order of O(log n).

Explanation algorithm:

In classic binary search, we compare val with the midpoint to figure out if val belongs on the low or the high side. The complication here is that the array is rotated and may have an inflection point. Consider, for example:

Array1: [10, 15, 20, 0, 5] Array2: [50, 5, 20, 30, 40]

Note that both arrays have a midpoint of 20, but 5 appears on the left side of one and on the right side of the other. Therefore, comparing val with the midpoint is insufficient.

However, if we look a bit deeper, we can see that one half of the array must be ordered normally(increasing order). We can therefore look at the normally ordered half to determine whether we should search the low or hight side.

For example, if we are searching for 5 in Array1, we can look at the left element (10) and middle element (20). Since 10 < 20, the left half must be ordered normally. And, since 5 is not between those, we know that we must search the right half

In array2, we can see that since 50 > 20, the right half must be ordered normally. We turn to the middle 20, and right 40 element to check if 5 would fall between them. The value 5 would not Therefore, we search the left half.

There are 2 possible solution: iterative and recursion. Recursion helps you understand better the above algorithm explanation

def search_rotate(array, val):
    low, high = 0, len(array) - 1
    while low <= high:
        mid = (low + high) // 2
        if val == array[mid]:
            return mid

        if array[low] <= array[mid]:
            if array[low] <= val <= array[mid]:
                high = mid - 1
            else:
                low = mid + 1
        else:
            if array[mid] <= val <= array[high]:
                low = mid + 1
            else:
                high = mid - 1

    return -1

# Recursion technique
def search_rotate_recur(array, low, high, val):
    if low >= high:
        return -1
    mid = (low + high) // 2
    if val == array[mid]:       # found element
        return mid
    if array[low] <= array[mid]:
        if array[low] <= val <= array[mid]:
            return search_rotate_recur(array, low, mid - 1, val)    # Search left
        else:
            return search_rotate_recur(array, mid + 1, high, val)   # Search right
    else:
        if array[mid] <= val <= array[high]:
            return search_rotate_recur(array, mid + 1, high, val)   # Search right
        else:
            return search_rotate_recur(array, low, mid - 1, val)    # Search left

17.11 two sum

Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number. The function two_sum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. You may assume that each input would have exactly one solution and you may not use the same element twice.

Input: numbers = [2, 7, 11, 15], target=9 Output: index1 = 1, index2 = 2

Solution: two_sum: using binary search two_sum1: using dictionary as a hash table two_sum2: using two pointers

# Using binary search technique
def two_sum(numbers, target):
    for i in range(len(numbers)):
        second_val = target - numbers[i]
        low, high = i+1, len(numbers)-1
        while low <= high:
            mid = low + (high - low) // 2
            if second_val == numbers[mid]:
                return [i + 1, mid + 1]
            elif second_val > numbers[mid]:
                low = mid + 1
            else:
                high = mid - 1

# Using dictionary as a hash table
def two_sum1(numbers, target):
    dic = {}
    for i, num in enumerate(numbers):
        if target - num in dic:
            return [dic[target - num] + 1, i + 1]
        dic[num] = i

# Using two pointers
def two_sum2(numbers, target):
    p1 = 0                      # pointer 1 holds from left of array numbers
    p2 = len(numbers) - 1       # pointer 2 holds from right of array numbers
    while p1 < p2:
        s = numbers[p1] + numbers[p2]
        if s == target:
            return [p1 + 1, p2 + 1]
        elif s > target:
            p2 = p2 - 1
        else:
            p1 = p1 + 1