beam: add rotated binary search with tests

This commit is contained in:
beam authored and ledger committed 2026-10-30 05:37:09 +00:00
1 parent 65657e3809
commit 03f41830e0
2 files changed
+115

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
"""Search in a rotated sorted array (possibly with duplicates).
Provides:
- find_pivot(nums): index of smallest element (rotation point), or -1 if not rotated
- search(nums, target): index of target or -1
- count_rotations(nums): number of rotations (== pivot index)
Handles empty, single element, duplicates, and arrays that are not rotated.
O(log n) typical; degrades to O(n) only when duplicates force linear scan
(standard limitation of rotated search with duplicates).
"""
from typing import List
def find_pivot(nums: List[int]) -> int:
"""Return index of the minimum element. -1 if nums is empty."""
if not nums:
return -1
lo, hi = 0, len(nums) - 1
# If already sorted (no rotation), pivot is 0.
if nums[lo] < nums[hi] or len(nums) == 1:
return 0
while lo < hi:
mid = (lo + hi) // 2
if nums[mid] > nums[hi]:
lo = mid + 1
elif nums[mid] < nums[hi]:
hi = mid
else:
# nums[mid] == nums[hi]: cannot decide; shrink hi
hi -= 1
return lo
def count_rotations(nums: List[int]) -> int:
p = find_pivot(nums)
return 0 if p < 0 else p
def search(nums: List[int], target: int) -> int:
if not nums:
return -1
pivot = find_pivot(nums)
n = len(nums)
# Map logical (sorted) index -> physical index.
def phys(i):
return (pivot + i) % n
lo, hi = 0, n - 1
while lo <= hi:
mid = (lo + hi) // 2
val = nums[phys(mid)]
if val == target:
return phys(mid)
if val < target:
lo = mid + 1
else:
hi = mid - 1
return -1
@@ -0,0 +1,56 @@
import random
from rotated_binary_search import find_pivot, search, count_rotations
def test_empty():
assert find_pivot([]) == -1
assert search([], 5) == -1
assert count_rotations([]) == 0
def test_single():
assert find_pivot([7]) == 0
assert search([7], 7) == 0
assert search([7], 3) == -1
def test_sorted_no_rotation():
a = [1,2,3,4,5]
assert find_pivot(a) == 0
for t in a + [0,6]:
idx = search(a, t)
assert (idx == -1) == (t not in a)
if idx != -1:
assert a[idx] == t
def test_rotated():
base = [1,2,3,4,5,6,7]
for r in range(len(base)):
arr = base[r:] + base[:r]
assert find_pivot(arr) == r, (r, find_pivot(arr))
assert count_rotations(arr) == r
for t in base:
i = search(arr, t)
assert i != -1 and arr[i] == t
def test_duplicates():
arr = [2,2,2,3,3,1,1,2]
# pivot should point at a minimum (value 1)
p = find_pivot(arr)
assert arr[p] == 1
assert search(arr, 3) != -1
assert search(arr, 9) == -1
def test_randomized():
random.seed(7)
for _ in range(2000):
n = random.randint(0, 30)
base = sorted(random.randint(-5,5) for _ in range(n))
r = random.randrange(n) if n else 0
arr = base[r:] + base[:r] if n else []
p = find_pivot(arr)
if n:
assert arr[p] == min(arr)
# search every element
for t in set(base):
i = search(arr, t)
assert i == -1 or arr[i] == t
# search absent
assert search(arr, 10**9) == -1