Facebook Pixel

744. Find Smallest Letter Greater Than Target

Problem Description

You have an array of characters letters that is sorted in non-decreasing order (alphabetically), and you're given a character target. The array contains at least two different characters.

Your task is to find the smallest character in letters that comes after target in alphabetical order (lexicographically greater). If no such character exists in the array, you should wrap around and return the first character in letters.

For example:

  • If letters = ['c', 'f', 'j'] and target = 'a', the answer is 'c' because it's the smallest character greater than 'a'.
  • If letters = ['c', 'f', 'j'] and target = 'd', the answer is 'f' because it's the smallest character greater than 'd'.
  • If letters = ['c', 'f', 'j'] and target = 'j', the answer is 'c' because no character in the array is greater than 'j', so we wrap around to the beginning.

The solution uses binary search with bisect_right to efficiently find the insertion point where target would go in the sorted array. The function returns the index of the first element greater than target. By taking this index modulo the array length (i % len(letters)), we handle the wrap-around case when no greater character exists.

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

Intuition

Since the array is already sorted, we can leverage this property to find our answer efficiently. Think of it like looking up a word in a dictionary - you don't need to check every single word; you can jump to approximately where you expect the word to be and then narrow down your search.

The key insight is that we need to find the first character that is strictly greater than our target. In a sorted array, all characters greater than target will be grouped together on the right side, and all characters less than or equal to target will be on the left side. This naturally forms a boundary that we can search for.

Binary search is perfect for this because it repeatedly divides the search space in half. We're essentially asking: "Where would target be inserted if we wanted to maintain the sorted order?" The answer to this question gives us the index of the first character greater than target.

The clever part about using bisect_right is that it finds the rightmost position where target could be inserted. This means it automatically gives us the index of the first element that is strictly greater than target. If target is 'd' and we have ['a', 'c', 'f', 'j'], bisect_right would return index 2 (pointing to 'f').

The wrap-around behavior is elegantly handled with the modulo operator (%). If all characters in the array are less than or equal to target, bisect_right returns the length of the array. Taking this modulo the array length gives us 0, which correctly points back to the first character - exactly what the problem asks for when no greater character exists.

Learn more about Binary Search patterns.

Solution Implementation

1class Solution:
2    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
3        """
4        Find the smallest character in letters that is lexicographically greater than target.
5        If no such character exists, return the first character in letters (wrap around).
6
7        Args:
8            letters: A sorted list of characters in non-decreasing order
9            target: The target character to find the next greatest letter for
10
11        Returns:
12            The smallest character greater than target, or the first character if none exists
13        """
14        n = len(letters)
15        left, right = 0, n - 1
16        first_true_index = -1
17
18        # Binary search to find the first index where letters[mid] > target
19        while left <= right:
20            mid = (left + right) // 2
21
22            # Feasible condition: is this character greater than target?
23            if letters[mid] > target:
24                first_true_index = mid  # Record potential answer
25                right = mid - 1  # Search left for smaller valid index
26            else:
27                left = mid + 1  # Search right
28
29        # Handle wrap-around: if no character is greater, return first character
30        if first_true_index == -1:
31            return letters[0]
32        return letters[first_true_index]
33
1class Solution {
2    public char nextGreatestLetter(char[] letters, char target) {
3        int n = letters.length;
4        int left = 0;
5        int right = n - 1;
6        int firstTrueIndex = -1;
7
8        // Binary search to find the first index where letters[mid] > target
9        while (left <= right) {
10            int mid = left + (right - left) / 2;
11
12            // Feasible condition: is this character greater than target?
13            if (letters[mid] > target) {
14                firstTrueIndex = mid;  // Record potential answer
15                right = mid - 1;  // Search left for smaller valid index
16            } else {
17                left = mid + 1;  // Search right
18            }
19        }
20
21        // Handle wrap-around: if no character is greater, return first character
22        if (firstTrueIndex == -1) {
23            return letters[0];
24        }
25        return letters[firstTrueIndex];
26    }
27}
28
1class Solution {
2public:
3    char nextGreatestLetter(vector<char>& letters, char target) {
4        int n = letters.size();
5        int left = 0;
6        int right = n - 1;
7        int firstTrueIndex = -1;
8
9        // Binary search to find the first index where letters[mid] > target
10        while (left <= right) {
11            int mid = left + (right - left) / 2;
12
13            // Feasible condition: is this character greater than target?
14            if (letters[mid] > target) {
15                firstTrueIndex = mid;  // Record potential answer
16                right = mid - 1;  // Search left for smaller valid index
17            } else {
18                left = mid + 1;  // Search right
19            }
20        }
21
22        // Handle wrap-around: if no character is greater, return first character
23        if (firstTrueIndex == -1) {
24            return letters[0];
25        }
26        return letters[firstTrueIndex];
27    }
28};
29
1/**
2 * Finds the smallest character in the sorted array that is lexicographically greater than the target.
3 * If no such character exists, returns the first character in the array (wraps around).
4 *
5 * @param letters - A sorted array of lowercase letters in non-decreasing order
6 * @param target - The target character to find the next greatest letter for
7 * @returns The smallest character greater than target, or the first character if none exists
8 */
9function nextGreatestLetter(letters: string[], target: string): string {
10  const n = letters.length;
11  let left = 0;
12  let right = n - 1;
13  let firstTrueIndex = -1;
14
15  // Binary search to find the first index where letters[mid] > target
16  while (left <= right) {
17    const mid = Math.floor((left + right) / 2);
18
19    // Feasible condition: is this character greater than target?
20    if (letters[mid] > target) {
21      firstTrueIndex = mid; // Record potential answer
22      right = mid - 1; // Search left for smaller valid index
23    } else {
24      left = mid + 1; // Search right
25    }
26  }
27
28  // Handle wrap-around: if no character is greater, return first character
29  if (firstTrueIndex === -1) {
30    return letters[0];
31  }
32  return letters[firstTrueIndex];
33}
34

Solution Approach

The solution uses binary search with the standard boundary-finding template to find the smallest character that is larger than target.

Defining the Feasible Function

For this problem, we define feasible(mid) as: "Is letters[mid] greater than target?"

This creates a boolean array pattern across indices:

letters:  ['c', 'f', 'j']
target:   'd'
feasible: [false, true, true]

We want to find the first true index - the smallest index where letters[mid] > target.

Binary Search Template

We use the standard template with first_true_index tracking:

  1. Initialize left = 0, right = n - 1 (last valid index), and first_true_index = -1
  2. While left <= right:
    • Calculate mid = (left + right) // 2
    • If letters[mid] > target (feasible):
      • Record first_true_index = mid as a potential answer
      • Search left half: right = mid - 1 (look for smaller valid index)
    • Else:
      • Search right half: left = mid + 1
  3. After the loop, first_true_index contains the index of the smallest character greater than target, or -1 if none exists

Handling the Wrap-Around

The problem requires wrapping to the first character if no greater character exists. We handle this by checking first_true_index:

  • If first_true_index != -1: return letters[first_true_index]
  • If first_true_index == -1: return letters[0] (wrap around)

This explicit handling is cleaner than using modulo arithmetic because it makes the wrap-around logic visible and intentional.

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 an example with letters = ['c', 'f', 'j'] and target = 'd'.

Step 1: Initialize

  • Array: ['c', 'f', 'j']
  • left = 0, right = 2 (last valid index)
  • first_true_index = -1

Step 2: First iteration (left=0, right=2)

  • Calculate mid = (0 + 2) // 2 = 1
  • Check letters[1] which is 'f'
  • Is 'f' > 'd'? Yes! (feasible)
  • Record first_true_index = 1
  • Search left: right = mid - 1 = 0

Step 3: Second iteration (left=0, right=0)

  • Calculate mid = (0 + 0) // 2 = 0
  • Check letters[0] which is 'c'
  • Is 'c' > 'd'? No! (not feasible)
  • Search right: left = mid + 1 = 1

Step 4: Loop ends (left=1 > right=0)

  • first_true_index = 1 (found a valid answer)
  • Return letters[1] which is 'f'

The answer is 'f', which is correct as it's the smallest character greater than 'd'.

Wrap-around example: letters = ['c', 'f', 'j'], target = 'j'

Step 1: Initialize

  • left = 0, right = 2, first_true_index = -1

Step 2: First iteration (left=0, right=2)

  • mid = 1, letters[1] = 'f'
  • Is 'f' > 'j'? No!
  • Search right: left = 2

Step 3: Second iteration (left=2, right=2)

  • mid = 2, letters[2] = 'j'
  • Is 'j' > 'j'? No!
  • Search right: left = 3

Step 4: Loop ends (left=3 > right=2)

  • first_true_index = -1 (no character greater than target found)
  • Wrap around: return letters[0] which is 'c'

This demonstrates how tracking first_true_index explicitly handles the wrap-around case when no character is greater than the target.

Time and Space Complexity

The time complexity is O(log n), where n is the length of the letters array. Binary search divides the search space in half with each iteration, resulting in logarithmic time.

The space complexity is O(1). The algorithm only uses a constant number of variables (left, right, mid, first_true_index) regardless of input size.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A common mistake is using while left < right with right = mid instead of the standard template. This variant works but is harder to reason about and inconsistent with the boundary-finding pattern.

Incorrect variant:

left, right = 0, len(letters)
while left < right:
    mid = (left + right) // 2
    if letters[mid] > target:
        right = mid  # Different from template
    else:
        left = mid + 1
return letters[left % len(letters)]

Correct template:

left, right = 0, len(letters) - 1
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if letters[mid] > target:
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return letters[0] if first_true_index == -1 else letters[first_true_index]

2. Forgetting the Wrap-Around Case

Without handling the case where no character is greater than target, you'll get an index out of bounds error or incorrect result.

Incorrect approach:

# Assumes first_true_index will always be found
return letters[first_true_index]  # Error when first_true_index == -1

Solution: Check for -1 and wrap to first character:

if first_true_index == -1:
    return letters[0]
return letters[first_true_index]

3. Wrong Feasible Condition

Using >= instead of > will return the target itself if it exists, instead of the next greater character.

Incorrect:

if letters[mid] >= target:  # Wrong: includes target itself
    first_true_index = mid
    right = mid - 1

Correct:

if letters[mid] > target:  # Correct: strictly greater
    first_true_index = mid
    right = mid - 1

4. Not Understanding "Smallest Greater"

Some might return any character greater than target. The problem asks for the smallest such character.

Example: If letters = ['a', 'c', 'f', 'j'] and target = 'b', returning 'f' or 'j' is wrong. The correct answer is 'c' as it's the smallest character greater than 'b'.

The binary search template naturally handles this by searching left (right = mid - 1) when we find a valid answer, ensuring we find the leftmost (smallest) valid index.

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

Which data structure is used in a depth first search?


Recommended Readings

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

Load More