{x}
blog image

Find Smallest Letter Greater Than Target

You are given an array of characters letters that is sorted in non-decreasing order, and a character target. There are at least two different characters in letters.

Return the smallest character in letters that is lexicographically greater than target. If such a character does not exist, return the first character in letters.

 

Example 1:

Input: letters = ["c","f","j"], target = "a"
Output: "c"
Explanation: The smallest character that is lexicographically greater than 'a' in letters is 'c'.

Example 2:

Input: letters = ["c","f","j"], target = "c"
Output: "f"
Explanation: The smallest character that is lexicographically greater than 'c' in letters is 'f'.

Example 3:

Input: letters = ["x","x","y","y"], target = "z"
Output: "x"
Explanation: There are no characters in letters that is lexicographically greater than 'z' so we return letters[0].

 

Constraints:

  • 2 <= letters.length <= 104
  • letters[i] is a lowercase English letter.
  • letters is sorted in non-decreasing order.
  • letters contains at least two different characters.
  • target is a lowercase English letter.

Solution Explanation: Find Smallest Letter Greater Than Target

This problem asks to find the smallest character in a sorted array letters that is lexicographically greater than a given character target. The solution leverages the sorted nature of the input array to efficiently find the answer using binary search.

Approach:

The core idea is to perform a binary search on the letters array. Binary search is efficient because it repeatedly divides the search interval in half. Instead of searching for an exact match, we modify the binary search to find the smallest element greater than the target.

  1. Initialization: We initialize two pointers, l (left) and r (right), to define the search space. l starts at 0, and r starts at the length of the letters array.

  2. Iteration: The while loop continues as long as the left pointer is less than the right pointer (l < r).

  3. Midpoint Calculation: In each iteration, we calculate the midpoint mid as (l + r) / 2 (or using bitwise shift (l+r)>>1 which is slightly faster).

  4. Comparison: We compare letters[mid] with target.

    • If letters[mid] is greater than target, it means the smallest greater element might be in the left half, so we update r to mid.
    • Otherwise, the smallest greater element must be in the right half, so we update l to mid + 1.
  5. Result: After the loop finishes, the left pointer l will point to the index of the smallest character greater than target. However, if no such character exists (all elements are less than or equal to target), l will point to the end of the array. To handle this case, we use the modulo operator (%) to wrap around to the beginning of the array if necessary (l % letters.length). This returns the first character in the array when no greater character is found.

Time Complexity: O(log n), where n is the length of the letters array. This is because binary search reduces the search space by half in each step.

Space Complexity: O(1). The algorithm uses a constant amount of extra space regardless of the input size.

Code Examples (with explanations):

The provided code in multiple languages all implement this binary search approach. Let's break down a Python example:

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        l, r = 0, len(letters)  # Initialize left and right pointers
        while l < r:             # Binary search loop
            mid = (l + r) // 2   # Calculate midpoint
            if letters[mid] > target:
                r = mid        # Search in the left half
            else:
                l = mid + 1    # Search in the right half
        return letters[l % len(letters)] #Return the result, handling wrap-around.
 

The other languages (Java, C++, Go, TypeScript, Rust, PHP) follow a very similar structure, adapting the syntax to the specific language but maintaining the same core binary search logic. The Arrays.binarySearch method in Java provides a built-in binary search function that we can utilize with slight modifications to achieve the same outcome. Similarly, C++ utilizes upper_bound which provides a more direct way of finding the element. The core algorithm remains the same across all implementations.