1533. Find the Index of the Large Integer 🔒
Problem Description
You have an integer array where all integers are equal except for one integer that is larger than all the others. You cannot access the array directly. Instead, you're given an ArrayReader
API with two functions:
-
compareSub(l, r, x, y)
: Compares the sum of elements in subarrayarr[l..r]
with the sum of elements in subarrayarr[x..y]
. It returns:1
ifsum(arr[l..r]) > sum(arr[x..y])
0
ifsum(arr[l..r]) == sum(arr[x..y])
-1
ifsum(arr[l..r]) < sum(arr[x..y])
-
length()
: Returns the size of the array.
Constraints:
- You can call
compareSub()
at most 20 times - Both functions work in
O(1)
time - Parameters must satisfy:
0 <= l, r, x, y < length
,l <= r
, andx <= y
Goal: Find and return the index of the larger integer in the array.
The challenge is to efficiently locate the single larger element using only subarray sum comparisons, without direct array access. Since all other elements are equal, any subarray containing the larger element will have a greater sum than an equal-length subarray without it.
Intuition
Since all elements are equal except for one larger element, we can use a divide-and-conquer strategy to locate it. The key insight is that if we split the array into segments and compare their sums, the segment containing the larger element will have a greater sum.
Think of it like finding a heavier ball among identical balls using a balance scale. If we divide the balls into groups and weigh them, the heavier group contains our target.
The solution uses a ternary search approach - dividing the search space into three parts at each step. Why three parts instead of two (like binary search)? Because with the compareSub
function, we can compare two subarrays in a single call, effectively checking three regions:
- The left third:
[left, t2]
- The middle third:
[t2+1, t3]
- The remaining part:
[t3+1, right]
By comparing the first two thirds with compareSub(t1, t2, t2+1, t3)
:
- If they're equal (return
0
), the larger element must be in the third part - If the left third is heavier (return
1
), the larger element is in the left third - If the middle third is heavier (return
-1
), the larger element is in the middle third
This approach eliminates roughly 2/3 of the search space with each comparison, requiring at most log₃(n)
comparisons. For an array of reasonable size, this stays well within the 20-call limit.
The beauty of this solution is that it leverages the fact that all elements except one are identical - any imbalance in sum directly points to where the larger element resides.
Learn more about Binary Search patterns.
Solution Approach
The implementation uses a ternary search algorithm with a while loop that continues until we narrow down to a single element.
Step-by-step implementation:
-
Initialize search boundaries:
left, right = 0, reader.length() - 1
Start with the entire array as our search space.
-
Main loop - continue while we have more than one element:
while left < right:
-
Divide the current range into three parts:
t1 = left t2 = left + (right - left) // 3 t3 = left + ((right - left) // 3) * 2 + 1
t1
tot2
: First third of the ranget2 + 1
tot3
: Second third (approximately equal size)t3 + 1
toright
: Remaining elements
-
Compare the first two thirds:
cmp = reader.compareSub(t1, t2, t2 + 1, t3)
This single comparison tells us which region contains the larger element.
-
Update search boundaries based on comparison result:
-
If
cmp == 0
(both subarrays have equal sum):left = t3 + 1
The larger element must be in the third part since the first two parts are equal.
-
If
cmp == 1
(left third has greater sum):right = t2
The larger element is in the left third
[t1, t2]
. -
If
cmp == -1
(middle third has greater sum):left, right = t2 + 1, t3
The larger element is in the middle third
[t2 + 1, t3]
.
-
-
Return the final position:
return left
When
left == right
, we've found the index of the larger element.
Complexity Analysis:
- Time Complexity:
O(log₃ n)
comparisons, as we eliminate approximately 2/3 of the search space with each iteration - Space Complexity:
O(1)
, using only a constant amount of extra variables - API Calls: At most
⌈log₃ n⌉
calls tocompareSub()
, which stays well under the 20-call limit for practical array sizes
The algorithm efficiently narrows down the search space by leveraging the property that equal-sized subarrays will have different sums only if one contains the larger element.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the ternary search algorithm locates the larger element.
Example array: [2, 2, 2, 5, 2, 2, 2]
(length = 7)
- All elements are 2 except for a 5 at index 3
- We need to find index 3 using only
compareSub()
calls
Initial State:
left = 0, right = 6
(searching entire array)
Iteration 1:
-
Calculate division points:
t1 = 0
t2 = 0 + (6-0)//3 = 2
t3 = 0 + ((6-0)//3)*2 + 1 = 5
-
This divides the array into three parts:
- Left third:
[0,2]
→[2, 2, 2]
(sum = 6) - Middle third:
[3,5]
→[5, 2, 2]
(sum = 9) - Remaining:
[6,6]
→[2]
(sum = 2)
- Left third:
-
Call
compareSub(0, 2, 3, 5)
:- Compares sum of
[2,2,2]
with sum of[5,2,2]
- Returns
-1
since 6 < 9
- Compares sum of
-
Since middle third has greater sum, update:
left = 3, right = 5
Iteration 2:
-
Calculate division points:
t1 = 3
t2 = 3 + (5-3)//3 = 3
t3 = 3 + ((5-3)//3)*2 + 1 = 4
-
This divides into:
- Left third:
[3,3]
→[5]
(sum = 5) - Middle third:
[4,4]
→[2]
(sum = 2) - Remaining:
[5,5]
→[2]
(sum = 2)
- Left third:
-
Call
compareSub(3, 3, 4, 4)
:- Compares sum of
[5]
with sum of[2]
- Returns
1
since 5 > 2
- Compares sum of
-
Since left third has greater sum, update:
right = 3
- Now
left = 3, right = 3
Result:
- Loop terminates since
left == right
- Return
3
- the index of the larger element
Total API calls: 2 calls to compareSub()
, well within the 20-call limit.
This example demonstrates how the algorithm efficiently narrows the search space by approximately 2/3 with each comparison, quickly zeroing in on the larger element's position.
Solution Implementation
1# """
2# This is ArrayReader's API interface.
3# You should not implement it, or speculate about its implementation
4# """
5# class ArrayReader(object):
6# # Compares the sum of arr[l..r] with the sum of arr[x..y]
7# # return 1 if sum(arr[l..r]) > sum(arr[x..y])
8# # return 0 if sum(arr[l..r]) == sum(arr[x..y])
9# # return -1 if sum(arr[l..r]) < sum(arr[x..y])
10# def compareSub(self, l: int, r: int, x: int, y: int) -> int:
11# pass
12#
13# # Returns the length of the array
14# def length(self) -> int:
15# pass
16
17
18class Solution:
19 def getIndex(self, reader: 'ArrayReader') -> int:
20 """
21 Find the index of the single different element in an array where all elements
22 are the same except one, using a ternary search approach.
23
24 The algorithm divides the search space into three parts and uses compareSub
25 to determine which part contains the different element.
26
27 Args:
28 reader: ArrayReader object with compareSub and length methods
29
30 Returns:
31 The index of the different element
32 """
33 # Initialize search boundaries
34 left = 0
35 right = reader.length() - 1
36
37 # Continue searching while we haven't narrowed down to a single element
38 while left < right:
39 # Divide the current range into three parts
40 # first_boundary: end of first third
41 first_boundary = left + (right - left) // 3
42 # second_boundary: start of last third
43 second_boundary = left + ((right - left) // 3) * 2 + 1
44
45 # Compare the sum of first third with the sum of middle third
46 comparison_result = reader.compareSub(
47 left, first_boundary, # First third: [left, first_boundary]
48 first_boundary + 1, second_boundary # Middle third: [first_boundary + 1, second_boundary]
49 )
50
51 if comparison_result == 0:
52 # Both thirds have equal sums, so the different element is in the last third
53 left = second_boundary + 1
54 elif comparison_result == 1:
55 # First third has larger sum, so the different element is in the first third
56 right = first_boundary
57 else:
58 # Middle third has larger sum, so the different element is in the middle third
59 left = first_boundary + 1
60 right = second_boundary
61
62 # When left == right, we've found the index of the different element
63 return left
64
1/**
2 * // This is ArrayReader's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * interface ArrayReader {
5 * // Compares the sum of arr[l..r] with the sum of arr[x..y]
6 * // return 1 if sum(arr[l..r]) > sum(arr[x..y])
7 * // return 0 if sum(arr[l..r]) == sum(arr[x..y])
8 * // return -1 if sum(arr[l..r]) < sum(arr[x..y])
9 * public int compareSub(int l, int r, int x, int y) {}
10 *
11 * // Returns the length of the array
12 * public int length() {}
13 * }
14 */
15
16class Solution {
17 /**
18 * Finds the index of a special element in the array using ternary search.
19 * The algorithm divides the search range into three parts and uses sum comparisons
20 * to determine which part contains the target element.
21 *
22 * @param reader The ArrayReader interface to access array operations
23 * @return The index of the special element
24 */
25 public int getIndex(ArrayReader reader) {
26 // Initialize search boundaries
27 int left = 0;
28 int right = reader.length() - 1;
29
30 // Continue searching while we have more than one element in range
31 while (left < right) {
32 // Divide the current range into three parts
33 // firstBoundary: end of first third
34 int firstBoundary = left + (right - left) / 3;
35
36 // secondBoundary: end of second third (start of last third)
37 int secondBoundary = left + (right - left) / 3 * 2 + 1;
38
39 // Compare the sum of first third with the sum of second third
40 int comparisonResult = reader.compareSub(left, firstBoundary,
41 firstBoundary + 1, secondBoundary);
42
43 if (comparisonResult == 0) {
44 // Sums are equal, so the special element is in the third part
45 left = secondBoundary + 1;
46 } else if (comparisonResult == 1) {
47 // First third has larger sum, special element is in first third
48 right = firstBoundary;
49 } else {
50 // Second third has larger sum, special element is in second third
51 left = firstBoundary + 1;
52 right = secondBoundary;
53 }
54 }
55
56 // Return the index of the special element
57 return left;
58 }
59}
60
1/**
2 * // This is the ArrayReader's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class ArrayReader {
5 * public:
6 * // Compares the sum of arr[l..r] with the sum of arr[x..y]
7 * // return 1 if sum(arr[l..r]) > sum(arr[x..y])
8 * // return 0 if sum(arr[l..r]) == sum(arr[x..y])
9 * // return -1 if sum(arr[l..r]) < sum(arr[x..y])
10 * int compareSub(int l, int r, int x, int y);
11 *
12 * // Returns the length of the array
13 * int length();
14 * };
15 */
16
17class Solution {
18public:
19 /**
20 * Finds the index of a special element in the array using ternary search.
21 * The algorithm divides the search range into three parts and uses sum comparisons
22 * to determine which part contains the target element.
23 *
24 * @param reader - ArrayReader object providing API to compare subarrays and get length
25 * @return The index of the special element in the array
26 */
27 int getIndex(ArrayReader& reader) {
28 // Initialize search boundaries
29 int left = 0;
30 int right = reader.length() - 1;
31
32 // Continue searching while we have more than one element in range
33 while (left < right) {
34 // Divide the current range into three parts
35 // Calculate boundary points for the three segments
36 int firstSegmentEnd = left + (right - left) / 3;
37 int secondSegmentStart = firstSegmentEnd + 1;
38 int secondSegmentEnd = left + (right - left) / 3 * 2 + 1;
39 int thirdSegmentStart = secondSegmentEnd + 1;
40
41 // Compare the sum of first segment with the sum of second segment
42 int comparisonResult = reader.compareSub(left, firstSegmentEnd,
43 secondSegmentStart, secondSegmentEnd);
44
45 if (comparisonResult == 0) {
46 // Sums are equal, so the special element must be in the third segment
47 left = thirdSegmentStart;
48 } else if (comparisonResult == 1) {
49 // First segment sum is greater, special element is in the first segment
50 right = firstSegmentEnd;
51 } else {
52 // Second segment sum is greater, special element is in the second segment
53 left = secondSegmentStart;
54 right = secondSegmentEnd;
55 }
56 }
57
58 // Return the index of the special element
59 return left;
60 }
61};
62
1/**
2 * This is the ArrayReader's API interface.
3 * You should not implement it, or speculate about its implementation
4 * interface ArrayReader {
5 * // Compares the sum of arr[l..r] with the sum of arr[x..y]
6 * // return 1 if sum(arr[l..r]) > sum(arr[x..y])
7 * // return 0 if sum(arr[l..r]) == sum(arr[x..y])
8 * // return -1 if sum(arr[l..r]) < sum(arr[x..y])
9 * compareSub(l: number, r: number, x: number, y: number): number;
10 *
11 * // Returns the length of the array
12 * length(): number;
13 * }
14 */
15
16/**
17 * Finds the index of a special element in the array using ternary search.
18 * The algorithm divides the search range into three parts and uses sum comparisons
19 * to determine which part contains the target element.
20 *
21 * @param reader - ArrayReader object providing API to compare subarrays and get length
22 * @returns The index of the special element in the array
23 */
24function getIndex(reader: ArrayReader): number {
25 // Initialize search boundaries
26 let left: number = 0;
27 let right: number = reader.length() - 1;
28
29 // Continue searching while we have more than one element in range
30 while (left < right) {
31 // Divide the current range into three parts
32 // Calculate boundary points for the three segments
33 const firstSegmentEnd: number = left + Math.floor((right - left) / 3);
34 const secondSegmentStart: number = firstSegmentEnd + 1;
35 const secondSegmentEnd: number = left + Math.floor((right - left) / 3 * 2) + 1;
36 const thirdSegmentStart: number = secondSegmentEnd + 1;
37
38 // Compare the sum of first segment with the sum of second segment
39 const comparisonResult: number = reader.compareSub(
40 left,
41 firstSegmentEnd,
42 secondSegmentStart,
43 secondSegmentEnd
44 );
45
46 if (comparisonResult === 0) {
47 // Sums are equal, so the special element must be in the third segment
48 left = thirdSegmentStart;
49 } else if (comparisonResult === 1) {
50 // First segment sum is greater, special element is in the first segment
51 right = firstSegmentEnd;
52 } else {
53 // Second segment sum is greater, special element is in the second segment
54 left = secondSegmentStart;
55 right = secondSegmentEnd;
56 }
57 }
58
59 // Return the index of the special element
60 return left;
61}
62
Time and Space Complexity
Time Complexity: O(log n)
The algorithm uses a ternary search approach to find the target index. In each iteration, the search space is divided into three parts, and based on the comparison result, approximately 2/3 of the search space is eliminated. The recurrence relation can be expressed as T(n) = T(n/3) + O(1)
, where the O(1)
represents the constant time for the compareSub
operation. Using the Master Theorem, this resolves to O(log₃ n)
, which is equivalent to O(log n)
since logarithms of different bases differ only by a constant factor.
The number of iterations is bounded by log₃(n)
since we reduce the problem size by a factor of 3 in each iteration, continuing until left >= right
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. The variables left
, right
, t1
, t2
, t3
, and cmp
are the only additional storage required, regardless of the input size. No recursive calls are made, and no additional data structures are used that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Calculation for Uneven Divisions
Pitfall: When dividing the search range into three parts, the calculation of boundaries can lead to unequal or overlapping segments, especially when (right - left)
is not perfectly divisible by 3.
Example Problem:
- If
left = 0, right = 4
(5 elements total) first_boundary = 0 + (4-0)//3 = 1
second_boundary = 0 + ((4-0)//3)*2 + 1 = 3
- This creates segments: [0,1], [2,3], [4,4]
- The segments have sizes 2, 2, and 1 - unequal comparison
Solution: Ensure equal-sized segments for comparison, and handle the remainder separately:
def getIndex(self, reader: 'ArrayReader') -> int:
left = 0
right = reader.length() - 1
while left < right:
size = right - left + 1
# Special case: if only 2 elements remain
if size == 2:
if reader.compareSub(left, left, right, right) == 1:
return left
else:
return right
# Calculate third size (ensuring equal comparison segments)
third_size = size // 3
# Define boundaries for equal-sized segments
mid1 = left + third_size - 1
mid2 = left + 2 * third_size - 1
# Compare first two equal-sized segments
cmp = reader.compareSub(left, mid1, mid1 + 1, mid2)
if cmp == 1:
right = mid1
elif cmp == -1:
left = mid1 + 1
right = mid2
else:
# Different element is in the remainder
left = mid2 + 1
return left
2. Edge Case: Array with 1 or 2 Elements
Pitfall: The ternary search logic breaks down when the array has very few elements, as dividing into three parts becomes impossible or inefficient.
Solution: Add special handling for small arrays:
def getIndex(self, reader: 'ArrayReader') -> int:
n = reader.length()
# Handle trivial case
if n == 1:
return 0
# Handle two-element case directly
if n == 2:
if reader.compareSub(0, 0, 1, 1) == 1:
return 0
else:
return 1
# Continue with ternary search for n >= 3
left, right = 0, n - 1
# ... rest of the ternary search logic
3. Inefficient Handling When Search Space is Not Divisible by 3
Pitfall: When the remaining search space size is 3k+1 or 3k+2, the original code might not optimally reduce the search space, potentially requiring more comparisons than necessary.
Solution: Optimize the division strategy based on the remainder:
def getIndex(self, reader: 'ArrayReader') -> int:
left, right = 0, reader.length() - 1
while left < right:
size = right - left + 1
if size <= 2:
# Binary comparison for small sizes
if size == 2:
if reader.compareSub(left, left, right, right) == 1:
return left
return right
return left
# Optimize division based on size modulo 3
if size % 3 == 0:
# Perfect thirds
third = size // 3
mid1 = left + third - 1
mid2 = left + 2 * third - 1
elif size % 3 == 1:
# Make first two parts equal, last part has one extra
third = size // 3
mid1 = left + third - 1
mid2 = left + 2 * third - 1
else: # size % 3 == 2
# Make first two parts equal, last part has two extra
third = size // 3
mid1 = left + third - 1
mid2 = left + 2 * third - 1
cmp = reader.compareSub(left, mid1, mid1 + 1, mid2)
if cmp == 1:
right = mid1
elif cmp == -1:
left = mid1 + 1
right = mid2
else:
left = mid2 + 1
return left
These solutions address the boundary calculation issues and edge cases that can cause the algorithm to fail or become inefficient in certain scenarios.
Which data structure is used to implement recursion?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!