Facebook Pixel

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:

  1. compareSub(l, r, x, y): Compares the sum of elements in subarray arr[l..r] with the sum of elements in subarray arr[x..y]. It returns:

    • 1 if sum(arr[l..r]) > sum(arr[x..y])
    • 0 if sum(arr[l..r]) == sum(arr[x..y])
    • -1 if sum(arr[l..r]) < sum(arr[x..y])
  2. 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, and x <= 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. The left third: [left, t2]
  2. The middle third: [t2+1, t3]
  3. 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:

  1. Initialize search boundaries:

    left, right = 0, reader.length() - 1

    Start with the entire array as our search space.

  2. Main loop - continue while we have more than one element:

    while left < right:
  3. Divide the current range into three parts:

    t1 = left
    t2 = left + (right - left) // 3
    t3 = left + ((right - left) // 3) * 2 + 1
    • t1 to t2: First third of the range
    • t2 + 1 to t3: Second third (approximately equal size)
    • t3 + 1 to right: Remaining elements
  4. Compare the first two thirds:

    cmp = reader.compareSub(t1, t2, t2 + 1, t3)

    This single comparison tells us which region contains the larger element.

  5. 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].

  6. 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 to compareSub(), 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 Evaluator

Example 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:

  1. Calculate division points:

    • t1 = 0
    • t2 = 0 + (6-0)//3 = 2
    • t3 = 0 + ((6-0)//3)*2 + 1 = 5
  2. 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)
  3. Call compareSub(0, 2, 3, 5):

    • Compares sum of [2,2,2] with sum of [5,2,2]
    • Returns -1 since 6 < 9
  4. Since middle third has greater sum, update:

    • left = 3, right = 5

Iteration 2:

  1. Calculate division points:

    • t1 = 3
    • t2 = 3 + (5-3)//3 = 3
    • t3 = 3 + ((5-3)//3)*2 + 1 = 4
  2. This divides into:

    • Left third: [3,3] → [5] (sum = 5)
    • Middle third: [4,4] → [2] (sum = 2)
    • Remaining: [5,5] → [2] (sum = 2)
  3. Call compareSub(3, 3, 4, 4):

    • Compares sum of [5] with sum of [2]
    • Returns 1 since 5 > 2
  4. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More