Data Structures and Algorithms

Finding the Alphabetically Largest Letter in a String

1 week, 6 days ago ; F(visit_count) + Value(1) views
Share this

Finding the Alphabetically Largest Letter in a String (Optimized Approach)

 

When solving problems related to string analysis, efficiency is key, especially when handling large inputs.

In this article, we will compare different implementations to find the alphabetically largest letter that appears in both uppercase and lowercase in a given string.

I will help you examine their time and space complexities and optimize the solution for the best performance.

Problem Statement

String S has uppercase & lowercase letters in English, and we need to return the largest letter (in uppercase) that appears in both forms. If no such letter exists, return "NO".

Example Scenarios

Example 1

Input: "aaBabcDaA"

Output: "B"

Example 2

Input: "Codility"

Output: "NO"

Example 3

Input: "WeTestCodErs"

Output: "T"

Initial Brute Force Approach

This initial approach checks every uppercase letter and verifies if its lowercase version exists in the string. However, checking membership in a string is an O(N) operation, making this inefficient.

# Brute force approach (O(N^2))
def largest_letter(s):

    max_val = -float("inf")

    max_str = ""

    for i in s: # O(N) - Iterates through the string once

        ord_val = ord(i) # O(1)

        if ord_val >= 97: # O(1) - Check if it's a lowercase letter

            continue

        if ord_val > max_val and i.lower() in s: # O(N) - Membership check in string

            max_val = ord_val

            max_str = i

    return max_str if len(max_str) > 0 else "NO" # O(1)

Time Complexity: O(N^2)

The nested check i.lower() in s runs in O(N), making the overall complexity O(N^2). It is inefficient for large inputs.

Space Complexity: O(1)

The solution only uses a few extra variables, making its space complexity constant.

Optimized Approach Using Sets

To improve efficiency, we can convert the string into a set. It allows O(1) lookups instead of O(N), reducing the overall time complexity to O(N).

# Optimized approach (O(N))
def largest_letter(s):

    max_val = -float("inf")
    max_str = ""

    s_set = set(s) # Convert string to a set for O(1) lookups

    for i in s: # O(N) iteration over the string

        ord_val = ord(i)

        if ord_val >= 97: # Ignore lowercase letters
            continue

        if ord_val > max_val and i.lower() in s_set: # O(1) lookup in set
            max_val = ord_val
            max_str = i

    return max_str if max_str else "NO"

Time Complexity: O(N)

  • Creating the set: O(N)
  • Iterating through the string: O(N)
  • Lookups in the set: O(1)

Space Complexity: O(N)

The additional space used is for the set, which, in the worst case, stores all N characters.

Further Optimization Using Two Sets

Another optimized approach leverages two sets—one for lowercase letters and one for uppercase letters. It allows efficient lookups and ensures we find the largest valid letter in O(1) constant time after the initial scan.

class Solution:

    def solution(self, S: str)-> str:

        lower_set = set()
        upper_set = set()


        for char in S: # O(N) - Iterates through the string once
            if char.islower():      # O(1) check
                lower_set.add(char) # O(1) insert
            else:
                upper_set.add(char) # O(1) insert


        for char in range(90, 64, -1): # O(26), (constant time)
            upper_char = chr(char)
            lower_char = chr(char + 32)

            
            if upper_char in upper_set and lower_char in lower_set: # O(1) lookups
                return upper_char # O(1)

        return "NO" # O(1)

 

Time Complexity: O(N)

 

It is optimal since we:

  • Scan the string once (O(N)) to populate sets.
  • Perform at most 26 iterations (O(1)) over uppercase letters.

Space Complexity: O(1)

Since the sets only store alphabetic characters (maximum of 52 letters), this is considered O(1) extra space.

Comparing All Approaches

Approach                                      Time Complexity                                     Space Complexity                           Performance

Brute Force                                       O(N^2)                                                            O(1)                                             Slow

Set-Based Optimization                O(N)                                                                O(N)                                             Faster

Two-Set Method                              O(N)                                                                 O(1)                                             Optimal

The Best Approach

The two-set approach is the most efficient as it runs in O(N) time and O(1) space. It makes it ideal for handling large inputs.

Next Steps

Try running these solutions with large test cases to see the difference in execution speed.

If you work with big data or performance-critical applications, always aim for O(N) or better solutions whenever possible!

Need help with more optimization problems? Follow for more insights into writing efficient code!

Become a member
Get the latest news right in your inbox. We never spam!

Read next

How to Check If Two Words Are Anagrams in Python

How to Check if Two Words are Anagrams in Python Clarity and precision are essential in sol… Read More

3 days, 3 hours ago . 254 views