beam: add rotated binary search with tests
This commit is contained in:
1 parent
65657e3809
commit
03f41830e0
2 files changed
+115
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user