1935. Maximum Number of Words You Can Type
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.
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:
-
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. -
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 returnsTrue
only when every character passes our test. -
The expression
c not in s for c in w
generates a boolean for each character in wordw
, checking if that character is NOT in the broken set. -
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 andFalse
as 0, sosum()
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 EvaluatorExample 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 takesO(|brokenLetters|)
time, which is at mostO(26)
=O(1)
since there are only 26 lowercase English letters text.split()
iterates through the entire string once, takingO(n)
time- For each word
w
in the split result, we check each characterc
against the sets
, which takesO(|w|)
time per word - The total time for checking all characters across all words is
O(n)
since we examine each character intext
at most once (excluding spaces) - Set membership testing (
c not in s
) isO(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), requiringO(26)
=O(|Σ|)
space text.split()
creates a list of words, which in worst case could beO(n)
space, but since we're using a generator expression withsum()
andall()
, the words are processed one at a time without storing the entire list- The generator expression and
all()
function useO(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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!