You are given a sorted array of integers, but the array's length is unknown and cannot be accessed directly. Instead, you have access to an interface called ArrayReader
, which provides a method get(index)
that returns the value at the given index. If you call get(index)
for an index that is out of bounds, it returns a large integer (such as 231 - 1
). Your task is to find the index of a given target
value in the array. If the target is not present, return -1
.
get()
.
The central challenge is that we do not know the array's length, so we cannot perform a classic binary search with fixed boundaries. A brute-force approach would be to call get(i)
for every index starting from 0
until we either find the target or hit an out-of-bounds value. However, this approach is inefficient, especially for large arrays.
Since the array is sorted, binary search is the ideal choice for searching efficiently. But first, we need to determine a reasonable upper bound for the search range. We can do this by "exponentially expanding" the search boundaries until we either find a value that is greater than or equal to the target or hit an out-of-bounds value.
Once we have a valid range, we can perform a standard binary search within that range. This two-stage process ensures we do not waste calls and maintain an efficient search.
right = 1
).
right *= 2
) until reader.get(right)
is greater than or equal to the target
, or until reader.get(right)
returns an out-of-bounds value.
left
(previous right) and right
.
left
and right
.
reader.get(mid)
:
target
, return mid
.target
or is out-of-bounds, move the right boundary left.target
, move the left boundary right.-1
.
We use exponential search to minimize the number of get()
calls needed to discover the valid search interval, and binary search to efficiently find the target within that interval.
Suppose the array is: [-1, 0, 3, 5, 9, 12, 15, 20]
(unknown length)
Target: 9
left = 0
, right = 1
right = 2
right = 4
Now, left = 2
(previous right), right = 4
The algorithm finds the target efficiently without knowing the array's length.
O(N)
(linear scan until target or out-of-bounds)O(1)
O(log P)
calls, where P
is the position of the target (since right boundary doubles each time).
O(log P)
as well.
O(log P)
O(1)
(no extra space used)
The key to this problem is handling the unknown array size. By combining exponential search (to find a valid search interval) with binary search (to efficiently locate the target), we achieve a highly efficient solution that minimizes the number of interface calls. This method leverages the sorted property of the array and avoids the pitfalls of linear scanning, making it both elegant and practical for large datasets.
# Leetcode interface
# class ArrayReader:
# def get(self, index: int) -> int:
class Solution:
def search(self, reader: 'ArrayReader', target: int) -> int:
left, right = 0, 1
# Exponential search to find range
while reader.get(right) < target:
left = right
right *= 2
# Binary search in found range
while left <= right:
mid = left + (right - left) // 2
val = reader.get(mid)
if val == target:
return mid
elif val > target or val == 2**31 - 1:
right = mid - 1
else:
left = mid + 1
return -1
/**
* // This is the ArrayReader's API interface.
* // You should not implement it, or speculate about its implementation
* class ArrayReader {
* public:
* int get(int index);
* };
*/
class Solution {
public:
int search(ArrayReader& reader, int target) {
int left = 0, right = 1;
// Exponential search to find bounds
while (reader.get(right) < target) {
left = right;
right *= 2;
}
// Binary search
while (left <= right) {
int mid = left + (right - left) / 2;
int val = reader.get(mid);
if (val == target) return mid;
else if (val > target || val == INT_MAX)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
};
/**
* // This is ArrayReader's API interface.
* // You should not implement it, or speculate about its implementation
* interface ArrayReader {
* public int get(int index) {}
* }
*/
class Solution {
public int search(ArrayReader reader, int target) {
int left = 0, right = 1;
// Exponential search to find bounds
while (reader.get(right) < target) {
left = right;
right *= 2;
}
// Binary search
while (left <= right) {
int mid = left + (right - left) / 2;
int val = reader.get(mid);
if (val == target) return mid;
else if (val > target || val == Integer.MAX_VALUE)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
}
/**
* // This is the ArrayReader's API interface.
* // function ArrayReader() {
* // @param {number} index
* // @return {number}
* // this.get = function(index) {...}
* // }
*/
/**
* @param {ArrayReader} reader
* @param {number} target
* @return {number}
*/
var search = function(reader, target) {
let left = 0, right = 1;
// Exponential search to find bounds
while (reader.get(right) < target) {
left = right;
right *= 2;
}
// Binary search
while (left <= right) {
let mid = left + Math.floor((right - left) / 2);
let val = reader.get(mid);
if (val === target) return mid;
else if (val > target || val === 2147483647)
right = mid - 1;
else
left = mid + 1;
}
return -1;
};