Binary Search

Ethan Davis
Data Structures and Algorithms DSA
3 min readApr 9, 2024
Data Structures and Algorithms

Statement

There’s an array of integers numbers, sorted in ascending order, and an integer target. If target exists in the array, then return its index, otherwise return -1.

Constraints

  • 1 ≤ numbers.length() ≤ 10³
  • -10⁴ ≤ numbers[i], target ≤ 10⁴
  • numbers[i] is unique

Solution

"""
production algorithm
"""


class Solution:
def binary_search(self, nums, target):
"""
time complexity O(logn)
space complexity O(1)
"""
low, high = 0, len(nums) - 1

while low <= high:
mid = (high + low) // 2
if nums[mid] == target:
return mid
if target < nums[mid]:
high = mid - 1
else:
low = mid + 1

return -1
"""
unit tests
"""

from unittest import TestCase
from algorithms.modified_binary_search.binary_search.solution import Solution


class SolutionTestCase(TestCase):
def test_target_is_first_middle(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 16
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = 10
self.assertEqual(actual, expected)

def test_target_is_left_of_first_middle(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 15
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = 9
self.assertEqual(actual, expected)

def test_target_is_right_of_first_middle(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 17
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = 11
self.assertEqual(actual, expected)

def test_target_is_start(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 1
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = 0
self.assertEqual(actual, expected)

def test_target_is_end(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 29
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = 19
self.assertEqual(actual, expected)

def test_target_nonexistent(self):
# Given
numbers = [1, 3, 4, 8, 9, 10, 11, 12, 13, 15,
16, 17, 18, 20, 21, 22, 23, 25, 26, 29]
target = 55
solution = Solution()

# When
actual = solution.binary_search(numbers, target)

# Then
expected = -1
self.assertEqual(actual, expected)

Strategy

Modified Binary Search.

Explanation

Initialize low and high pointers as the low and high indexes of the array. Then while low is less than or equal to high, calculate the middle index between them. Then there’s three cases.

If the target integer is at the middle index, then return the middle index. Otherwise, if the target integer is left of the middle index, then set high as the middle index minus one, and search the left subarray. Finally, if the target integer is right of the middle index, then set low as the middle index plus one, and search the right subarray.

If target doesn’t exist in the array, i.e. low and high pass each other such that high < low and so a subarray cannot exist, then return -1.

Time Complexity

The middle of a subarray can be calculated a maximum of logn times, where n is the size of the input array. That’s because the array can be divided in half a maximum of logn times, before there are no further subarrays, i.e. when high becomes less than low. Therefore, the time complexity of the algorithm is O(logn).

Space Complexity

The algorithm only uses constant auxiliary space to find the target integer index. Therefore, the auxiliary space complexity of the algorithm is O(1).

Links

https://github.com/davisethan/data_structures_algorithms/tree/main/algorithms/modified_binary_search/binary_search

--

--