Facebook Pixel

748. Shortest Completing Word

EasyArrayHash TableString
Leetcode Link

Problem Description

You are given a license plate string licensePlate and an array of strings words. Your task is to find the shortest "completing word" from the words array.

A completing word is defined as a word that contains all the letters from the licensePlate. When checking for a completing word:

  • Ignore all numbers and spaces in the licensePlate
  • Treat all letters as case-insensitive (uppercase and lowercase are considered the same)
  • If a letter appears multiple times in licensePlate, the completing word must contain that letter at least the same number of times

For example, if licensePlate = "aBc 12c":

  • After removing numbers and spaces, we have letters: 'a', 'b', 'c', 'c' (note that 'c' appears twice)
  • Valid completing words could be "abccdef" (contains a, b, and at least 2 c's), "caaacab" (contains a, b, and at least 2 c's), or "cbca" (contains a, b, and 2 c's)

The function should return the shortest completing word from the words array. If there are multiple completing words with the same shortest length, return the one that appears first in the words array.

The problem guarantees that at least one completing word exists in the input.

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

Intuition

The key insight is that we need to check if each word contains at least the required frequency of each letter from the license plate. This naturally leads us to think about frequency counting.

First, we need to extract and count the letters from the license plate. Since we only care about letters (not numbers or spaces) and the comparison is case-insensitive, we can convert everything to lowercase and count only alphabetic characters. This gives us a frequency map of required letters.

For each word in our list, we need to verify if it "covers" all the required letters with their frequencies. A word is valid if for every letter in our license plate frequency map, the word contains at least that many occurrences of that letter. This is essentially checking if the word's frequency map is a "superset" of the license plate's frequency map.

To find the shortest completing word, we can iterate through all words and keep track of the shortest valid one found so far. An optimization here is that if we already found a valid word of length n, we can skip checking any word with length ≥ n since we're looking for the shortest one. This pruning helps reduce unnecessary frequency counting operations.

The "first occurrence" requirement for ties is naturally handled by processing the words in order and only updating our answer when we find a strictly shorter valid word (not equal length), ensuring that among words of the same length, the first one encountered is kept.

Solution Approach

We implement the solution using frequency counting with hash tables (Python's Counter).

Step 1: Count letters in license plate

cnt = Counter(c.lower() for c in licensePlate if c.isalpha())

We iterate through each character in licensePlate, converting to lowercase and keeping only alphabetic characters. The Counter creates a frequency map where keys are letters and values are their counts.

Step 2: Process each word

ans = None
for w in words:

Initialize the answer as None and iterate through each word in the words array.

Step 3: Early termination optimization

if ans and len(w) >= len(ans):
    continue

If we already have a valid answer and the current word's length is greater than or equal to the answer's length, skip this word since we're looking for the shortest completing word.

Step 4: Count letters in current word

t = Counter(w)

Create a frequency map for the current word w.

Step 5: Check if word is completing

if all(v <= t[c] for c, v in cnt.items()):
    ans = w

For each letter c with frequency v in the license plate, check if the word contains at least v occurrences of that letter. The expression v <= t[c] verifies this condition. If the word doesn't contain letter c, t[c] returns 0, making the comparison false.

The all() function returns True only if every letter requirement is satisfied. When we find such a word, we update our answer.

Step 6: Return the result

return ans

The algorithm has a time complexity of O(n × m) where n is the number of words and m is the average length of words, since we potentially check each word and count its characters. The space complexity is O(1) for the frequency maps since we only store at most 26 letters.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with licensePlate = "1s3 PSt" and words = ["step", "steps", "stripe", "stepple"].

Step 1: Extract and count letters from license plate

  • From "1s3 PSt", we extract only letters: 's', 'P', 'S', 't'
  • Convert to lowercase: 's', 'p', 's', 't'
  • Count frequencies: cnt = {'s': 2, 'p': 1, 't': 1}

Step 2: Check each word

Word 1: "step"

  • Length: 4
  • No answer yet, so we proceed
  • Count letters: {'s': 1, 't': 1, 'e': 1, 'p': 1}
  • Check requirements:
    • Need 's': 2, have 1 ❌ (1 < 2)
    • Since not all requirements are met, skip this word

Word 2: "steps"

  • Length: 5
  • No answer yet, so we proceed
  • Count letters: {'s': 2, 't': 1, 'e': 1, 'p': 1}
  • Check requirements:
    • Need 's': 2, have 2 ✓ (2 ≥ 2)
    • Need 'p': 1, have 1 ✓ (1 ≥ 1)
    • Need 't': 1, have 1 ✓ (1 ≥ 1)
  • All requirements met! Set ans = "steps"

Word 3: "stripe"

  • Length: 6
  • We have an answer of length 5, and 6 ≥ 5
  • Skip this word (optimization)

Word 4: "stepple"

  • Length: 7
  • We have an answer of length 5, and 7 ≥ 5
  • Skip this word (optimization)

Result: Return "steps" as the shortest completing word.

Note how the optimization allowed us to skip checking the last two words entirely, saving us from unnecessary frequency counting operations.

Solution Implementation

1from collections import Counter
2from typing import List
3
4class Solution:
5    def shortestCompletingWord(self, licensePlate: str, words: List[str]) -> str:
6        # Count the frequency of each letter in the license plate (case-insensitive)
7        license_char_count = Counter(
8            char.lower() for char in licensePlate if char.isalpha()
9        )
10      
11        # Initialize the result as None
12        result = None
13      
14        # Iterate through each word in the words list
15        for word in words:
16            # Skip if we already have a result and current word is longer or equal
17            if result and len(word) >= len(result):
18                continue
19          
20            # Count the frequency of characters in the current word
21            word_char_count = Counter(word)
22          
23            # Check if the current word contains all required characters
24            # with at least the required frequency
25            if all(count <= word_char_count[char] 
26                   for char, count in license_char_count.items()):
27                result = word
28      
29        return result
30
1class Solution {
2    public String shortestCompletingWord(String licensePlate, String[] words) {
3        // Count frequency of each letter in license plate (case-insensitive)
4        int[] licensePlateFrequency = new int[26];
5        for (int i = 0; i < licensePlate.length(); i++) {
6            char currentChar = licensePlate.charAt(i);
7            if (Character.isLetter(currentChar)) {
8                // Convert to lowercase and count the frequency
9                licensePlateFrequency[Character.toLowerCase(currentChar) - 'a']++;
10            }
11        }
12      
13        // Initialize result with empty string
14        String shortestWord = "";
15      
16        // Check each word in the array
17        for (String currentWord : words) {
18            // Skip if we already found a shorter or equal length word
19            if (!shortestWord.isEmpty() && currentWord.length() >= shortestWord.length()) {
20                continue;
21            }
22          
23            // Count frequency of each letter in current word
24            int[] currentWordFrequency = new int[26];
25            for (int i = 0; i < currentWord.length(); i++) {
26                currentWordFrequency[currentWord.charAt(i) - 'a']++;
27            }
28          
29            // Check if current word contains all required letters with sufficient frequency
30            boolean isCompletingWord = true;
31            for (int i = 0; i < 26; i++) {
32                if (currentWordFrequency[i] < licensePlateFrequency[i]) {
33                    isCompletingWord = false;
34                    break;
35                }
36            }
37          
38            // Update result if current word is a completing word
39            if (isCompletingWord) {
40                shortestWord = currentWord;
41            }
42        }
43      
44        return shortestWord;
45    }
46}
47
1class Solution {
2public:
3    string shortestCompletingWord(string licensePlate, vector<string>& words) {
4        // Count frequency of each letter in the license plate (case-insensitive)
5        int licenseFreq[26]{};
6        for (char& ch : licensePlate) {
7            if (isalpha(ch)) {
8                ++licenseFreq[tolower(ch) - 'a'];
9            }
10        }
11      
12        // Initialize result string to store the shortest completing word
13        string result;
14      
15        // Check each word in the words array
16        for (auto& word : words) {
17            // Skip if we already found a shorter or equal length answer
18            if (!result.empty() && result.size() <= word.size()) {
19                continue;
20            }
21          
22            // Count frequency of each letter in the current word
23            int wordFreq[26]{};
24            for (char& ch : word) {
25                ++wordFreq[ch - 'a'];
26            }
27          
28            // Check if current word contains all required letters with sufficient frequency
29            bool isCompleting = true;
30            for (int i = 0; i < 26; ++i) {
31                if (licenseFreq[i] > wordFreq[i]) {
32                    isCompleting = false;
33                    break;
34                }
35            }
36          
37            // Update result if current word is a completing word
38            if (isCompleting) {
39                result = word;
40            }
41        }
42      
43        return result;
44    }
45};
46
1/**
2 * Finds the shortest word from the words array that contains all the letters from the license plate.
3 * @param licensePlate - The license plate string containing letters and possibly numbers/spaces
4 * @param words - Array of words to search through
5 * @returns The shortest completing word
6 */
7function shortestCompletingWord(licensePlate: string, words: string[]): string {
8    // Initialize frequency counter for 26 lowercase letters
9    const letterFrequency: number[] = Array(26).fill(0);
10  
11    // Count frequency of each letter in the license plate (case-insensitive)
12    for (const char of licensePlate) {
13        const charIndex = char.toLowerCase().charCodeAt(0) - 97; // Convert to 0-25 range
14        // Only count if it's a valid letter (a-z)
15        if (charIndex >= 0 && charIndex < 26) {
16            letterFrequency[charIndex]++;
17        }
18    }
19  
20    // Initialize result with empty string
21    let shortestWord = '';
22  
23    // Check each word in the words array
24    for (const word of words) {
25        // Skip if we already have a shorter or equal length answer
26        if (shortestWord.length > 0 && shortestWord.length <= word.length) {
27            continue;
28        }
29      
30        // Count letter frequency in current word
31        const wordLetterFrequency = Array(26).fill(0);
32        for (const char of word) {
33            const charIndex = char.charCodeAt(0) - 97;
34            wordLetterFrequency[charIndex]++;
35        }
36      
37        // Check if current word contains all required letters with sufficient frequency
38        let isValidWord = true;
39        for (let i = 0; i < 26; i++) {
40            if (wordLetterFrequency[i] < letterFrequency[i]) {
41                isValidWord = false;
42                break;
43            }
44        }
45      
46        // Update result if current word is valid
47        if (isValidWord) {
48            shortestWord = word;
49        }
50    }
51  
52    return shortestWord;
53}
54

Time and Space Complexity

Time Complexity: O(n × m + n × |Σ|) which simplifies to O(n × (m + |Σ|))

The algorithm consists of:

  • Creating a counter for the license plate: O(p) where p is the length of licensePlate
  • Iterating through each word in words array: O(n) iterations
  • For each word:
    • Checking length comparison: O(1)
    • Creating a counter for the word: O(m) where m is the average length of words
    • Checking if all required characters are present: O(|Σ|) where |Σ| = 26 (lowercase letters)

Since we process each word and for each word we perform O(m + |Σ|) operations, the total time complexity is O(n × (m + |Σ|)). When considering that m is typically small and comparable to |Σ|, this can be simplified to O(n × |Σ|) where |Σ| = 26.

Space Complexity: O(|Σ|)

The space usage includes:

  • Counter for license plate characters: O(|Σ|) at most 26 unique lowercase letters
  • Counter for each word (reused in each iteration): O(|Σ|) at most 26 unique lowercase letters
  • Variable to store the answer: O(1)

The dominant space usage is O(|Σ|) where |Σ| = 26 for the character set of lowercase letters.

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

Common Pitfalls

1. Not Handling Case Sensitivity Properly

A frequent mistake is forgetting to convert the license plate letters to lowercase while the words array might contain lowercase letters, or vice versa. This causes valid completing words to be incorrectly rejected.

Incorrect approach:

# Forgetting to normalize case
license_char_count = Counter(char for char in licensePlate if char.isalpha())

Correct approach:

# Convert to lowercase for case-insensitive comparison
license_char_count = Counter(char.lower() for char in licensePlate if char.isalpha())

2. Incorrectly Checking Character Frequencies

Another common error is checking only if the word contains the required letters without verifying the correct frequency. For example, if the license plate has two 'c's, the completing word must have at least two 'c's.

Incorrect approach:

# Only checks if character exists, not the frequency
if all(char in word for char in license_char_count):
    result = word

Correct approach:

# Properly checks that each character appears at least as many times as required
word_char_count = Counter(word)
if all(count <= word_char_count[char] for char, count in license_char_count.items()):
    result = word

3. Not Preserving Order When Multiple Words Have Same Length

When multiple completing words have the same shortest length, the problem requires returning the first one in the array. Using incorrect data structures or sorting can break this requirement.

Incorrect approach:

# Sorting changes the original order
words.sort(key=len)
for word in words:
    # Check completion...

Correct approach:

# Process words in their original order
for word in words:
    if result and len(word) >= len(result):
        continue
    # Check completion...

4. Including Non-Alphabetic Characters in the Count

The problem explicitly states to ignore numbers and spaces, but it's easy to accidentally include them in the character count.

Incorrect approach:

# Counts all characters including digits and spaces
license_char_count = Counter(licensePlate.lower())

Correct approach:

# Filters out non-alphabetic characters
license_char_count = Counter(char.lower() for char in licensePlate if char.isalpha())
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More