Facebook Pixel

1935. Maximum Number of Words You Can Type

EasyHash TableString
Leetcode Link

Problem Description

You have a keyboard where some letter keys are broken and don't work, while all other keys function normally.

Given two inputs:

  • text: a string containing words separated by single spaces (with no leading or trailing spaces)
  • brokenLetters: a string containing all the distinct letters that are broken on the keyboard

Your task is to count how many complete words from text can be typed using this malfunctioning keyboard. A word can only be typed if none of its letters appear in the broken letters list.

For example, if brokenLetters = "ab" and text = "hello world", you can type both words since neither contains 'a' or 'b', so the answer would be 2. But if text = "leet code" with the same broken letters, you could only type "leet" (not "code" which contains 'a'), so the answer would be 1.

The solution uses a set to store broken letters for O(1) lookup time, then checks each word in the text. For each word, it verifies that none of its characters appear in the broken letters set. The all() function returns True only if every character in the word is not broken, and the sum() counts how many words satisfy this condition.

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 each word independently to see if it can be typed. A word is "typeable" only if it doesn't contain any broken letters.

Think of it like a filter problem: we're filtering out words that contain forbidden characters. The natural approach is to:

  1. First, create a quick way to check if a letter is broken. Using a set for brokenLetters gives us O(1) lookup time for each character check.

  2. For each word, we need to verify that ALL its characters are typeable (not broken). This is a perfect use case for the all() function - it returns True only when every character passes our test.

  3. The expression c not in s for c in w generates a boolean for each character in word w, checking if that character is NOT in the broken set.

  4. Since all() returns a boolean (True if the word is typeable, False otherwise), we can directly sum these boolean values. In Python, True counts as 1 and False as 0, so sum() gives us the total count of typeable words.

The elegance of this solution comes from recognizing that counting valid words is equivalent to summing boolean results - each word either contributes 1 (if typeable) or 0 (if not) to our final count. This transforms the problem from explicit counting with conditionals to a simple summation of boolean expressions.

Solution Approach

We implement the solution using a hash table (set) to store broken letters and iterate through each word to check if it can be typed.

Step 1: Create a Set for Broken Letters

s = set(brokenLetters)

We convert the brokenLetters string into a set s. This gives us O(1) time complexity for checking if a character is broken, which is more efficient than repeatedly searching through the original string.

Step 2: Process Each Word

text.split()

We split the input text by spaces to get individual words. Since the problem guarantees no leading or trailing spaces and single spaces between words, split() without arguments works perfectly.

Step 3: Check Each Word for Broken Letters

all(c not in s for c in w)

For each word w, we use a generator expression (c not in s for c in w) to check every character c in the word. This generates a sequence of boolean values - True if the character is not broken, False if it is broken.

The all() function returns:

  • True only if ALL characters in the word are not in the broken set (meaning the word can be typed)
  • False if ANY character is broken (meaning the word cannot be typed)

Step 4: Count Valid Words

sum(all(c not in s for c in w) for w in text.split())

We use sum() to count how many words can be typed. Since all() returns a boolean value for each word (True = 1, False = 0), summing these values gives us the total count of typeable words.

Time Complexity: O(n) where n is the total number of characters in text, as we visit each character once.

Space Complexity: O(m) where m is the number of broken letters, for storing the set.

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 the solution with a concrete example to see how it works step by step.

Given:

  • text = "hello world leet code"
  • brokenLetters = "aeo"

Step 1: Create the broken letters set

s = set("aeo")  # s = {'a', 'e', 'o'}

Step 2: Split text into words

words = text.split()  # ["hello", "world", "leet", "code"]

Step 3: Check each word

For word "hello":

  • Check 'h': not in {'a', 'e', 'o'} → True
  • Check 'e': in {'a', 'e', 'o'} → False
  • Check 'l': not in {'a', 'e', 'o'} → True
  • Check 'l': not in {'a', 'e', 'o'} → True
  • Check 'o': in {'a', 'e', 'o'} → False
  • all([True, False, True, True, False])False (can't type "hello")

For word "world":

  • Check 'w': not in {'a', 'e', 'o'} → True
  • Check 'o': in {'a', 'e', 'o'} → False
  • Check 'r': not in {'a', 'e', 'o'} → True
  • Check 'l': not in {'a', 'e', 'o'} → True
  • Check 'd': not in {'a', 'e', 'o'} → True
  • all([True, False, True, True, True])False (can't type "world")

For word "leet":

  • Check 'l': not in {'a', 'e', 'o'} → True
  • Check 'e': in {'a', 'e', 'o'} → False
  • Check 'e': in {'a', 'e', 'o'} → False
  • Check 't': not in {'a', 'e', 'o'} → True
  • all([True, False, False, True])False (can't type "leet")

For word "code":

  • Check 'c': not in {'a', 'e', 'o'} → True
  • Check 'o': in {'a', 'e', 'o'} → False
  • Check 'd': not in {'a', 'e', 'o'} → True
  • Check 'e': in {'a', 'e', 'o'} → False
  • all([True, False, True, False])False (can't type "code")

Step 4: Sum the results

sum([False, False, False, False])  # 0 + 0 + 0 + 0 = 0

Result: 0 words can be typed

Let's try another example where some words CAN be typed:

  • text = "cat dog fish"
  • brokenLetters = "e"
  • Words check: "cat" (✓), "dog" (✓), "fish" (✓)
  • All three words contain no 'e', so the answer is 3

Solution Implementation

1class Solution:
2    def canBeTypedWords(self, text: str, brokenLetters: str) -> int:
3        # Convert broken letters string to a set for O(1) lookup
4        broken_letters_set = set(brokenLetters)
5      
6        # Split text into individual words
7        words = text.split()
8      
9        # Count words that can be typed (no broken letters in them)
10        typeable_count = 0
11      
12        # Check each word
13        for word in words:
14            # Check if all characters in the word are typeable
15            can_type_word = True
16            for char in word:
17                if char in broken_letters_set:
18                    can_type_word = False
19                    break
20          
21            # If word can be typed, increment counter
22            if can_type_word:
23                typeable_count += 1
24      
25        return typeable_count
26
1class Solution {
2    public int canBeTypedWords(String text, String brokenLetters) {
3        // Create a boolean array to mark which letters are broken
4        // Index represents letter position (0 for 'a', 1 for 'b', etc.)
5        boolean[] isBrokenLetter = new boolean[26];
6      
7        // Mark all broken letters as true in the array
8        for (char letter : brokenLetters.toCharArray()) {
9            isBrokenLetter[letter - 'a'] = true;
10        }
11      
12        // Counter for words that can be typed
13        int typableWordCount = 0;
14      
15        // Split the text into words and check each word
16        for (String word : text.split(" ")) {
17            // Assume the word can be typed initially
18            boolean canTypeWord = true;
19          
20            // Check each character in the current word
21            for (char character : word.toCharArray()) {
22                // If any character is a broken letter, the word cannot be typed
23                if (isBrokenLetter[character - 'a']) {
24                    canTypeWord = false;
25                    break;
26                }
27            }
28          
29            // Increment count if the word can be typed
30            if (canTypeWord) {
31                typableWordCount++;
32            }
33        }
34      
35        return typableWordCount;
36    }
37}
38
1class Solution {
2public:
3    int canBeTypedWords(string text, string brokenLetters) {
4        // Create a boolean array to mark which letters are broken
5        // Index represents letter position (0 for 'a', 1 for 'b', etc.)
6        bool isBroken[26] = {false};
7      
8        // Mark all broken letters in the array
9        for (char letter : brokenLetters) {
10            isBroken[letter - 'a'] = true;
11        }
12      
13        // Split the text into words and count typeable words
14        int typeableWordCount = 0;
15        vector<string> words = split(text, ' ');
16      
17        // Check each word to see if it can be typed
18        for (const string& word : words) {
19            bool canTypeWord = true;
20          
21            // Check if any character in the word is broken
22            for (char character : word) {
23                if (isBroken[character - 'a']) {
24                    canTypeWord = false;
25                    break;
26                }
27            }
28          
29            // Increment count if word can be typed
30            if (canTypeWord) {
31                typeableWordCount++;
32            }
33        }
34      
35        return typeableWordCount;
36    }
37
38private:
39    // Helper function to split a string by a delimiter character
40    vector<string> split(const string& inputString, char delimiter) {
41        vector<string> result;
42        string currentWord;
43      
44        // Iterate through each character in the input string
45        for (char currentChar : inputString) {
46            if (currentChar == delimiter) {
47                // Found delimiter, add current word to result
48                result.push_back(currentWord);
49                currentWord.clear();
50            } else {
51                // Build the current word
52                currentWord.push_back(currentChar);
53            }
54        }
55      
56        // Add the last word (no delimiter after it)
57        result.push_back(currentWord);
58      
59        return result;
60    }
61};
62
1/**
2 * Counts how many words in the text can be typed using a keyboard with broken letters
3 * @param text - The input text containing space-separated words
4 * @param brokenLetters - String containing all the broken letters on the keyboard
5 * @returns The number of words that can be fully typed
6 */
7function canBeTypedWords(text: string, brokenLetters: string): number {
8    // Create a boolean array to mark which letters are broken (indexed by letter position in alphabet)
9    const isBrokenLetter: boolean[] = Array(26).fill(false);
10  
11    // Mark all broken letters as true in the array
12    for (const letter of brokenLetters) {
13        const alphabetIndex = letter.charCodeAt(0) - 'a'.charCodeAt(0);
14        isBrokenLetter[alphabetIndex] = true;
15    }
16  
17    // Initialize counter for typeable words
18    let typeableWordCount = 0;
19  
20    // Split text into words and check each word
21    const words = text.split(' ');
22    for (const word of words) {
23        // Assume word is typeable initially
24        let canTypeWord = true;
25      
26        // Check if any letter in the word is broken
27        for (const letter of word) {
28            const alphabetIndex = letter.charCodeAt(0) - 'a'.charCodeAt(0);
29            if (isBrokenLetter[alphabetIndex]) {
30                canTypeWord = false;
31                break;
32            }
33        }
34      
35        // Increment count if word can be typed
36        if (canTypeWord) {
37            typeableWordCount++;
38        }
39    }
40  
41    return typeableWordCount;
42}
43

Time and Space Complexity

The time complexity is O(n), where n is the length of the string text. This is because:

  • Converting brokenLetters to a set takes O(|brokenLetters|) time, which is at most O(26) = O(1) since there are only 26 lowercase English letters
  • text.split() iterates through the entire string once, taking O(n) time
  • For each word w in the split result, we check each character c against the set s, which takes O(|w|) time per word
  • The total time for checking all characters across all words is O(n) since we examine each character in text at most once (excluding spaces)
  • Set membership testing (c not in s) is O(1) on average

The space complexity is O(|Σ|), where |Σ| is the size of the alphabet. In this problem, |Σ| = 26. This is because:

  • The set s stores at most 26 unique characters (all lowercase English letters), requiring O(26) = O(|Σ|) space
  • text.split() creates a list of words, which in worst case could be O(n) space, but since we're using a generator expression with sum() and all(), the words are processed one at a time without storing the entire list
  • The generator expression and all() function use O(1) additional space

Therefore, the dominant space factor is the set storage, giving us O(|Σ|) space complexity.

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

Common Pitfalls

1. Not Handling Empty brokenLetters String

A common mistake is not considering the case when brokenLetters is an empty string. When creating a set from an empty string, it results in an empty set, which is actually correct behavior - no letters are broken, so all words can be typed. However, some might try to add unnecessary special case handling.

Incorrect approach:

if not brokenLetters:
    return len(text.split())  # Unnecessary special case
broken_letters_set = set(brokenLetters)

Correct approach:

broken_letters_set = set(brokenLetters)  # Works correctly even when empty

2. Using String Search Instead of Set Lookup

Some might use in brokenLetters directly instead of converting to a set first. This leads to worse time complexity.

Inefficient approach:

for char in word:
    if char in brokenLetters:  # O(k) lookup where k = len(brokenLetters)
        can_type_word = False

Efficient approach:

broken_letters_set = set(brokenLetters)
for char in word:
    if char in broken_letters_set:  # O(1) lookup
        can_type_word = False

3. Incorrect Use of any() Instead of all()

A subtle logical error occurs when mixing up any() and all(). Some might write:

Incorrect:

sum(any(c not in s for c in w) for w in text.split())

This counts words that have AT LEAST ONE typeable character, not words where ALL characters are typeable.

Correct:

sum(all(c not in s for c in w) for w in text.split())

4. Not Breaking Early in Loop

When checking if a word can be typed, once we find a broken letter, we should immediately stop checking that word.

Inefficient approach:

for word in words:
    broken_count = 0
    for char in word:
        if char in broken_letters_set:
            broken_count += 1  # Continues checking even after finding broken letter
    if broken_count == 0:
        typeable_count += 1

Efficient approach:

for word in words:
    can_type_word = True
    for char in word:
        if char in broken_letters_set:
            can_type_word = False
            break  # Stop checking once we find a broken letter

5. Case Sensitivity Assumptions

The problem doesn't specify case handling. If the keyboard has a broken 'A', can we type 'a'? Without clarification, the solution treats uppercase and lowercase as different characters. If case-insensitive matching is needed:

Case-sensitive (default):

broken_letters_set = set(brokenLetters)

Case-insensitive (if required):

broken_letters_set = set(brokenLetters.lower())
# And check: if char.lower() in broken_letters_set
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

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

Load More