Quiz 2

04. Searching Algorithms — Linear Search & Binary Search

1516 words
8 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 04. Searching Algorithms — Linear Search & Binary Search > **What problem does this solve?** Given a list of items, find whether a specific value exists in it, and if so, at what position.

04. Searching Algorithms — Linear Search & Binary Search

What problem does this solve? Given a list of items, find whether a specific value exists in it, and if so, at what position. This is the most fundamental algorithmic operation — every program that looks up data needs search.

1. Linear Search (Unordered Data)

Mental Model

You're looking for a specific exam paper in an unsorted pile. You check each paper one by one until you find it or reach the bottom of the pile.

How It Works

(Diagram)

Implementation

python
# runnable
def linear_search(arr, target):
    """Return index of target in arr, or -1 if not found.
    Time: O(n) — must check every element in worst case
    Space: O(1) — no extra memory needed
    """
    for i in range(len(arr)):
        if arr[i] == target:
            return i      # Found at index i
    return -1              # Not found
# Test
arr = [64, 34, 25, 12, 22, 11, 90]
print(f"Search 12: found at index {linear_search(arr, 12)}")  # 3
print(f"Search 99: found at index {linear_search(arr, 99)}")  # -1

Complexity Analysis

CaseWhenComparisonsComplexity
BestTarget is first element1(O(1))
AverageTarget is in the middle(n/2)(O(n))
WorstTarget not present(n)(O(n))

2. Binary Search (Sorted Data)

Mental Model

You're looking up a word in a dictionary. You don't start at page 1 and scan — you open to the middle. If the word is alphabetically before that page, you search the first half; otherwise, the second half. Each step cuts the search space in half.

How It Works

(Diagram)

Implementation (Iterative)

python
# runnable
def binary_search_iterative(arr, target):
    """Return index of target in sorted arr, or -1 if not found.
    Time: O(log n) — search space halves each iteration
    Space: O(1)
    """
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1      # Search right half
        else:
            right = mid - 1     # Search left half
    return -1
# Test
sorted_arr = [11, 12, 22, 25, 34, 64, 90]
print(f"Search 25: index {binary_search_iterative(sorted_arr, 25)}")  # 3
print(f"Search 99: index {binary_search_iterative(sorted_arr, 99)}")  # -1

Implementation (Recursive)

python
# runnable
def binary_search_recursive(arr, target, left=None, right=None):
    """Recursive binary search."""
    if left is None:
        left, right = 0, len(arr) - 1
    if left > right:
        return -1
    mid = (left + right) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_recursive(arr, target, mid + 1, right)
    else:
        return binary_search_recursive(arr, target, left, mid - 1)
print(binary_search_recursive(sorted_arr, 34))  # 4

Step-by-Step Trace

Searching for 34 in [11, 12, 22, 25, 34, 64, 90]:
Stepleftrightmidarr[mid]ComparisonAction
10632525 < 34Search right
24656464 > 34Search left
34443434 == 34Found!
Searching for 99 in [11, 12, 22, 25, 34, 64, 90]:
Stepleftrightmidarr[mid]ComparisonAction
10632525 < 99right
24656464 < 99right
36669090 < 99right
476left > right → not found

Finding First/Last Occurrence of Duplicates

python
# runnable
def first_occurrence(arr, target):
    """Find first index of target in sorted array with duplicates."""
    left, right = 0, len(arr) - 1
    result = -1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            result = mid        # Record this occurrence
            right = mid - 1     # But keep searching left
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return result
def last_occurrence(arr, target):
    """Find last index of target in sorted array with duplicates."""
    left, right = 0, len(arr) - 1
    result = -1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            result = mid        # Record this occurrence
            left = mid + 1      # But keep searching right
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return result
# Test
arr = [1, 2, 3, 3, 3, 3, 4, 5]
print(f"First 3: {first_occurrence(arr, 3)}")   # 2
print(f"Last 3: {last_occurrence(arr, 3)}")     # 5
print(f"Count of 3: {last_occurrence(arr, 3) - first_occurrence(arr, 3) + 1}")  # 4

3. Comparison Table

FeatureLinear SearchBinary Search
Data requirementAny listSorted list
Best case(O(1)) — first element(O(1)) — middle element
Average case(O(n))(O(\log n))
Worst case(O(n))(O(\log n))
Space(O(1))(O(1)) iterative, (O(\log n)) recursive
Number of comparisons (n=1000)Up to 1000At most 10
StableYesYes
Can find first/last occurrenceYes (linear scan)Yes (modified binary search)

4. Common Bugs & Pitfalls

python
# BUG 1: Integer overflow (not in Python, but in languages with fixed-width ints)
# Python handles arbitrary precision, but the formula matters:
mid = (left + right) // 2         # Fine in Python
# Safer (for other languages):
mid = left + (right - left) // 2  # Avoids overflow
# BUG 2: Off-by-one in bounds
def buggy_bs(arr, target):
    left, right = 0, len(arr)  # Bug: should be len(arr) - 1
    while left < right:         # Bug: should be <=
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid          # Bug: should be mid + 1
        else:
            right = mid         # Bug: should be mid - 1
    return -1
# BUG 3: Not checking if the list is sorted
# Binary search on unsorted data will give wrong results
binary_search_iterative([3, 1, 4, 1, 5, 9], 4)  # May return -1 or wrong index
# BUG 4: Infinite loop with adjacent elements
def infinite_bs(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid          # Should be mid + 1
        else:
            right = mid         # Should be mid - 1
    return -1
# When left=0, right=1, mid=0:
# If target > arr[0], left becomes 0 again → infinite loop!

5. Practice Questions

Q1. Trace binary search for finding 7 in [1, 3, 5, 7, 9, 11, 13, 15]. Show left, right, mid values at each step. Q2. Write a function count_occurrences(arr, target) that counts how many times target appears in a sorted array, using binary search. Complexity should be O(log n). Q3. What is the minimum number of comparisons needed to find a value in a sorted array of 1 million elements? Q4. Modify binary search to find the peek element in a bitonic array (increasing then decreasing). Example: [1, 3, 8, 12, 4, 2] → peek = 12 at index 3. Q5. Linear search on average compares n/2 elements. True or false? Justify. Q6. Can you use binary search on a linked list? Why or why not? Q7. You have an array of unknown length (in a language where accessing out-of-bounds throws). How would you find the length to then binary search? Q8. What is the recurrence for binary search? Solve it. Q9. Write a function that uses binary search to find the square root of an integer (floor) without using math.sqrt. Q10. You have 1000 names sorted alphabetically. Binary search takes at most 10 comparisons. What if the list has 1,000,000 names?
Answers
A1.
Stepleftrightmidarr[mid]Action
10737Found!
Only 1 comparison in the best case when target is the middle element.
A2.
python
def count_occurrences(arr, target):
    first = first_occurrence(arr, target)
    if first == -1:
        return 0
    last = last_occurrence(arr, target)
    return last - first + 1
A3. (\log_2(1,000,000) \approx 20) comparisons (since (2^{20} = 1,048,576)).
A4.
python
def find_peek(arr):
    left, right = 0, len(arr) - 1
    while left < right:
        mid = (left + right) // 2
        if arr[mid] > arr[mid + 1]:
            right = mid
        else:
            left = mid + 1
    return left
A5. True. In the average case, target is found at position n/2 after n/2 comparisons. If target isn't present, all n elements are compared.
A6. No. Linked lists don't have O(1) random access — finding the middle element requires O(n) traversal, eliminating the benefit of halving.
A7. Use exponential search: check indices 1, 2, 4, 8, 16, ... until out-of-bounds, then binary search between last valid index and the overflow index.
A8. (T(n) = T(n/2) + 1). Unwinding: (T(n) = T(n/2^k) + k). When (n/2^k = 1), (k = \log n), so (T(n) = T(1) + \log n = O(\log n)).
A9.
python
def sqrt_floor(n):
    if n < 2:
        return n
    left, right = 1, n // 2
    while left <= right:
        mid = (left + right) // 2
        sq = mid * mid
        if sq == n:
            return mid
        elif sq < n:
            left = mid + 1
        else:
            right = mid - 1
    return right
A10. (\log_2(1,000,000) \approx 20). Adding 900,000 names only adds ~10 comparisons. This demonstrates the power of logarithmic growth. Join Discord Previous03. Complexity Analysis — Recurrence Relations & Master TheoremNext05. Selection Sort & Insertion Sort
Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.