Skip to content

Searching & Sorting ​

Fundamental algorithms for ordered data operations


Overview ​

Searching and sorting algorithms form the bedrock of computer science and are essential for any technical interview. These algorithms teach fundamental concepts like divide-and-conquer, time-space tradeoffs, and algorithm optimization that apply across all areas of programming.

Why master these algorithms?

  • Ubiquitous application: Nearly every real-world system relies on efficient data organization and retrieval
  • Interview frequency: Quick Sort, Merge Sort, and Binary Search are among the most commonly tested algorithms at top tech companies
  • Foundation for advanced topics: Understanding these basics unlocks more complex algorithms like graph traversal and dynamic programming
  • Optimization mindset: Learning when to use O(n log n) vs O(n^2) algorithms develops critical thinking about efficiency

Document Structure ​

TopicTypeComplexityLink
Sorting AlgorithmsConceptVariousLink
Binary SearchConceptO(log n)Link
Search in Rotated ArrayPatternO(log n)Link
Two Pointer TechniquePatternO(n)Link
Sliding WindowPatternO(n)Link
Counting Sort & Radix SortConceptO(n + k)Link
Quick SelectConceptO(n) avgLink
Merge IntervalsPatternO(n log n)Link

Sorting Algorithm Comparison ​

AlgorithmBestAverageWorstSpaceStableIn-Place
Bubble SortO(n)O(n^2)O(n^2)O(1)YesYes
Selection SortO(n^2)O(n^2)O(n^2)O(1)NoYes
Insertion SortO(n)O(n^2)O(n^2)O(1)YesYes
Merge SortO(n log n)O(n log n)O(n log n)O(n)YesNo
Quick SortO(n log n)O(n log n)O(n^2)O(log n)NoYes
Heap SortO(n log n)O(n log n)O(n log n)O(1)NoYes
Counting SortO(n + k)O(n + k)O(n + k)O(k)YesNo
Radix SortO(nk)O(nk)O(nk)O(n + k)YesNo
Tim SortO(n)O(n log n)O(n log n)O(n)YesNo

Note: Tim Sort (and its successor Powersort in Python 3.11+) is the default sorting algorithm in Python and Java for objects. Java uses Dual-Pivot Quicksort for primitives.


When to Use Which Sort ​

Decision Flowchart ​

Algorithm Selection Guide ​

ScenarioRecommended AlgorithmReason
General purposeQuick SortBest average-case performance
Guaranteed O(n log n)Merge Sort / Heap SortNo worst-case degradation
Nearly sorted dataInsertion SortO(n) for nearly sorted
Memory constrainedHeap SortO(1) auxiliary space
Stable sort requiredMerge SortMaintains relative order
Small integer rangeCounting SortLinear time O(n + k)
Linked list sortingMerge SortNo random access needed
External sortingMerge SortEfficient for disk I/O
Parallel processingMerge SortNaturally parallelizable

Python Sorting Tips ​

python
# Built-in sorted() vs list.sort()
arr = [3, 1, 4, 1, 5, 9, 2, 6]

sorted_arr = sorted(arr)  # Returns NEW sorted list, original unchanged
arr.sort()                # Sorts IN-PLACE, returns None

# Custom key function
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
sorted(students, key=lambda x: x[1])  # Sort by score
sorted(students, key=lambda x: x[1], reverse=True)  # Descending

# Multiple sort criteria
data = [("Alice", 85, 22), ("Bob", 85, 20), ("Charlie", 78, 25)]
sorted(data, key=lambda x: (-x[1], x[2]))  # By score desc, then age asc

# Using operator module (faster)
from operator import itemgetter, attrgetter
sorted(students, key=itemgetter(1))  # Sort by second element

# Sorting with custom objects
from functools import total_ordering

@total_ordering
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __eq__(self, other):
        return self.score == other.score

    def __lt__(self, other):
        return self.score < other.score

# Sort by absolute value
sorted([-5, 2, -3, 1, -4], key=abs)  # [1, 2, -3, -4, -5]

# Case-insensitive string sort
sorted(["Banana", "apple", "Cherry"], key=str.lower)

Binary Search Variants ​

Binary search goes beyond simple element lookup. Mastering these variants is crucial for interviews.

python
def binary_search(arr, target):
    left, right = 0, len(arr) - 1

    while left <= right:
        mid = left + (right - left) // 2  # Prevents overflow

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

    return -1  # Not found

Find Left Bound (First Occurrence) ​

python
def find_left_bound(arr, target):
    """Find the first occurrence of target."""
    left, right = 0, len(arr) - 1
    result = -1

    while left <= right:
        mid = left + (right - left) // 2

        if arr[mid] == target:
            result = mid
            right = mid - 1  # Continue searching left
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return result

Find Right Bound (Last Occurrence) ​

python
def find_right_bound(arr, target):
    """Find the last occurrence of target."""
    left, right = 0, len(arr) - 1
    result = -1

    while left <= right:
        mid = left + (right - left) // 2

        if arr[mid] == target:
            result = mid
            left = mid + 1  # Continue searching right
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return result

Using Python's bisect Module ​

python
import bisect

arr = [1, 2, 2, 2, 3, 4, 5]

# bisect_left: leftmost position to insert target
bisect.bisect_left(arr, 2)   # Returns 1 (first 2's position)

# bisect_right: rightmost position to insert target
bisect.bisect_right(arr, 2)  # Returns 4 (after last 2)

# Insert while maintaining sorted order
bisect.insort_left(arr, 2.5)  # Inserts 2.5 in correct position

Binary Search on Answer Space ​

python
def find_minimum_capacity(weights, days):
    """
    Find minimum ship capacity to ship all packages within 'days'.
    Example of binary search on answer space.
    """
    left = max(weights)       # Minimum possible capacity
    right = sum(weights)      # Maximum possible capacity

    def can_ship(capacity):
        current_load = 0
        days_needed = 1
        for weight in weights:
            if current_load + weight > capacity:
                days_needed += 1
                current_load = weight
            else:
                current_load += weight
        return days_needed <= days

    while left < right:
        mid = left + (right - left) // 2
        if can_ship(mid):
            right = mid
        else:
            left = mid + 1

    return left

Common Interview Patterns ​

Pattern 1: Search in Rotated Sorted Array ​

python
def search_rotated(nums, target):
    left, right = 0, len(nums) - 1

    while left <= right:
        mid = left + (right - left) // 2

        if nums[mid] == target:
            return mid

        # Left half is sorted
        if nums[left] <= nums[mid]:
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        # Right half is sorted
        else:
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1

    return -1

Pattern 2: Find Peak Element ​

python
def find_peak(nums):
    left, right = 0, len(nums) - 1

    while left < right:
        mid = left + (right - left) // 2

        if nums[mid] < nums[mid + 1]:
            left = mid + 1  # Peak is on the right
        else:
            right = mid     # Peak is on the left (or mid)

    return left
python
def search_matrix(matrix, target):
    """
    Search in a row-wise and column-wise sorted matrix.
    Start from top-right corner.
    """
    if not matrix or not matrix[0]:
        return False

    rows, cols = len(matrix), len(matrix[0])
    row, col = 0, cols - 1

    while row < rows and col >= 0:
        if matrix[row][col] == target:
            return True
        elif matrix[row][col] > target:
            col -= 1
        else:
            row += 1

    return False

Interview Focus Areas ​

Based on interview patterns at top tech companies, focus on these areas:

High Priority Topics ​

  1. Binary Search Variations: First/last occurrence, rotated arrays, 2D matrix search
  2. Quick Sort Implementation: Understanding partition logic and pivot selection
  3. Merge Sort: Especially for linked lists and external sorting scenarios
  4. Custom Comparators: Writing complex sorting logic with multiple criteria
  5. Binary Search on Answer Space: Finding optimal values (e.g., minimum capacity problems)

Common Mistakes to Avoid ​

  • Off-by-one errors: Carefully handle left <= right vs left < right
  • Integer overflow: Use left + (right - left) // 2 instead of (left + right) // 2
  • Edge cases: Empty arrays, single elements, all duplicates
  • Infinite loops: Ensure search space shrinks in every iteration

Interview Tips ​

  1. Clarify constraints first: Is the array sorted? Are there duplicates? What to return if not found?
  2. Discuss complexity upfront: Explain why you're choosing a particular algorithm
  3. Test with edge cases: Empty input, single element, all same elements, target at boundaries
  4. Consider stability requirements: Ask if relative order matters for equal elements

Practice Problems ​

Sorting ​

Sort Colors - Dutch National Flag (LeetCode 75)

Problem: Given an array with elements 0, 1, and 2 (red, white, blue), sort them in-place so same colors are adjacent in order 0, 1, 2.

Key Insight: Three-way partition using three pointers: low (boundary for 0s), mid (current), and high (boundary for 2s).

Approach: If nums[mid] == 0, swap with low and advance both. If nums[mid] == 2, swap with high and decrement high only. If 1, just advance mid.

Complexity: O(n) time (single pass), O(1) space

Merge Intervals (LeetCode 56)

Problem: Given an array of intervals, merge all overlapping intervals and return non-overlapping intervals.

Key Insight: After sorting by start time, two intervals overlap if current.start <= previous.end.

Approach: Sort by start time. For each interval, if it overlaps with the last merged interval, extend the end; otherwise, add it as a new interval.

Complexity: O(n log n) time (sorting dominates), O(n) space for output

Largest Number (LeetCode 179)

Problem: Given a list of non-negative integers, arrange them to form the largest possible number (as a string).

Key Insight: Custom comparator: for numbers a and b, compare strings a+b vs b+a. If "330" > "303", then 3 should come before 30.

Approach: Convert to strings, sort using custom comparator (a, b) -> compare(b+a, a+b). Handle edge case where result starts with "0".

Complexity: O(n log n * k) time where k is average digit length, O(n) space

Meeting Rooms II (LeetCode 253)

Problem: Given meeting time intervals, return the minimum number of conference rooms required.

Key Insight: Track when rooms become free using a min-heap of end times. If earliest ending meeting ends before new meeting starts, reuse that room.

Approach: Sort by start time. Use min-heap to track room end times. For each meeting, if heap[0] <= start, pop (room freed). Always push current end time.

Complexity: O(n log n) time, O(n) space for heap

Kth Largest Element (LeetCode 215)

Problem: Find the kth largest element in an unsorted array (not the kth distinct element).

Key Insight: QuickSelect achieves O(n) average by only recursing into the partition containing the target index.

Approach: Partition array around pivot. If pivot index equals target, done. If pivot index > target, recurse left; otherwise recurse right. Use random pivot to avoid O(n^2) worst case.

Complexity: O(n) average, O(n^2) worst case time; O(1) space

Search in Rotated Sorted Array (LeetCode 33)

Problem: Search for a target in a rotated sorted array with distinct values.

Key Insight: At any point, at least one half of the array is sorted. Identify which half is sorted, then check if target lies within that range.

Approach: Compare nums[left] with nums[mid] to find sorted half. Check if target is in sorted half's range to decide search direction.

Complexity: O(log n) time, O(1) space

Find First and Last Position (LeetCode 34)

Problem: Find the starting and ending position of a target value in a sorted array with duplicates.

Key Insight: Use two binary searches: one for lower bound (first occurrence) and one for upper bound (after last occurrence).

Approach: Left bound: shrink right when nums[mid] >= target. Right bound: shrink left when nums[mid] <= target. Subtract 1 from right bound result.

Complexity: O(log n) time, O(1) space

Search a 2D Matrix (LeetCode 74)

Problem: Search for a value in an m x n matrix where each row is sorted and first element of each row > last element of previous row.

Key Insight: Treat the 2D matrix as a 1D sorted array. Convert 1D index to 2D: row = idx // cols, col = idx % cols.

Approach: Binary search on range [0, m*n - 1]. Convert mid to 2D coordinates to compare values.

Complexity: O(log(m*n)) time, O(1) space

Find Peak Element (LeetCode 162)

Problem: Find any peak element (strictly greater than neighbors). Boundaries treated as negative infinity.

Key Insight: If nums[mid] < nums[mid+1], there must be a peak to the right (rising slope leads to peak or boundary).

Approach: Binary search comparing mid with mid+1. Move toward the ascending direction. Guaranteed to find peak.

Complexity: O(log n) time, O(1) space

Median of Two Sorted Arrays (LeetCode 4)

Problem: Find the median of two sorted arrays in O(log(min(m,n))) time.

Key Insight: Binary search to find partition point in smaller array. Corresponding partition in larger array is determined by half-length constraint.

Approach: For partition i in nums1, partition j in nums2 is (m+n+1)/2 - i. Valid when maxLeft1 <= minRight2 and maxLeft2 <= minRight1.

Complexity: O(log(min(m,n))) time, O(1) space

Capacity to Ship Packages (LeetCode 1011)

Problem: Find minimum ship capacity to ship all packages within D days.

Key Insight: Binary search on answer (capacity). Feasibility is monotonic: if capacity X works, any capacity > X also works.

Approach: Search space [max(weights), sum(weights)]. For each capacity, greedily count days needed. Find minimum capacity where days <= D.

Complexity: O(n * log(sum)) time, O(1) space


References ​