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.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
.
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.
Iteration: The while
loop continues as long as the left pointer is less than the right pointer (l < r
).
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).
Comparison: We compare letters[mid]
with target
.
letters[mid]
is greater than target
, it means the smallest greater element might be in the left half, so we update r
to mid
.l
to mid + 1
.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.