1684. Count the Number of Consistent Strings
Problem Description
You are given a string allowed
that contains distinct characters and an array of strings called words
.
A string is considered consistent if every character in that string appears in the allowed
string.
Your task is to count how many strings in the words
array are consistent.
For example:
- If
allowed = "ab"
andwords = ["ad", "bd", "aaab", "baa", "badab"]
"aaab"
is consistent because it only contains 'a' and 'b', which are both inallowed
"baa"
is consistent because it only contains 'a' and 'b', which are both inallowed
"ad"
is not consistent because it contains 'd', which is not inallowed
"bd"
is not consistent because it contains 'd', which is not inallowed
"badab"
is not consistent because it contains 'd', which is not inallowed
- The answer would be 2 (two consistent strings)
The solution uses a set to store all characters from allowed
for O(1) lookup time. Then it iterates through each word in words
, checking if all characters in that word exist in the set. The all()
function returns True
if every character c
in word w
is found in set s
. The sum()
function counts how many True
values (consistent strings) there are.
Intuition
The key insight is that we need to check if each word is made up entirely of characters that exist in allowed
. This is essentially a membership checking problem - for every character in a word, we need to verify it's a member of the allowed character set.
Since we'll be checking membership repeatedly (once for each character in each word), we want this operation to be as fast as possible. Converting allowed
into a set gives us O(1) lookup time for each character check, rather than O(n) if we searched through the string each time.
The thought process flows naturally:
- We need to validate each word individually - this suggests iterating through the
words
array - For each word, we need to ensure ALL its characters are valid - this suggests checking every character
- A word is consistent only if every single character passes the test - if even one character isn't in
allowed
, the entire word fails
This "all or nothing" requirement makes the all()
function a perfect fit - it returns True
only when every character in the word exists in our allowed set. We can then use sum()
to count the True
values (Python treats True
as 1 and False
as 0), giving us the total count of consistent strings in a single, elegant line.
The beauty of this approach is its simplicity - we're essentially filtering and counting in one pass through the data, with optimal time complexity for the membership checks.
Solution Approach
The solution uses a Hash Table (implemented as a Python set) to efficiently check character membership.
Step-by-step implementation:
-
Convert
allowed
string to a set:s = set(allowed)
This transformation takes O(m) time where m is the length of
allowed
, but gives us O(1) lookup time for each character check later. -
Iterate through each word and validate:
return sum(all(c in s for c in w) for w in words)
Breaking this down:
for w in words
: Iterate through each word in the arrayfor c in w
: For each word, check every characterc in s
: Test if characterc
exists in our sets
(O(1) operation)all(...)
: ReturnsTrue
only if ALL characters in the word are found in the setsum(...)
: Counts the number ofTrue
values (consistent strings)
Time Complexity Analysis:
- Creating the set: O(m) where m = length of
allowed
- Checking all words: O(n × k) where n = number of words, k = average length of each word
- Total: O(m + n × k)
Space Complexity:
- O(m) for storing the set of allowed characters
The algorithm efficiently handles the validation by:
- Pre-processing the allowed characters into a set for fast lookups
- Using Python's built-in
all()
function to short-circuit - if any character isn't in the set, it immediately returnsFalse
without checking the remaining characters - Leveraging
sum()
with a generator expression to count in a single pass without creating intermediate lists
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 a small example to illustrate the solution approach.
Given:
allowed = "abc"
words = ["a", "b", "cb", "abd", "dd"]
Step 1: Convert allowed string to a set
s = set("abc") = {'a', 'b', 'c'}
Step 2: Check each word for consistency
Let's trace through each word:
-
Word: "a"
- Check: Is 'a' in {'a', 'b', 'c'}? → Yes ✓
- All characters valid → Consistent (count = 1)
-
Word: "b"
- Check: Is 'b' in {'a', 'b', 'c'}? → Yes ✓
- All characters valid → Consistent (count = 2)
-
Word: "cb"
- Check: Is 'c' in {'a', 'b', 'c'}? → Yes ✓
- Check: Is 'b' in {'a', 'b', 'c'}? → Yes ✓
- All characters valid → Consistent (count = 3)
-
Word: "abd"
- Check: Is 'a' in {'a', 'b', 'c'}? → Yes ✓
- Check: Is 'b' in {'a', 'b', 'c'}? → Yes ✓
- Check: Is 'd' in {'a', 'b', 'c'}? → No ✗
- Not all characters valid → Not Consistent (count = 3)
-
Word: "dd"
- Check: Is 'd' in {'a', 'b', 'c'}? → No ✗
- (Short-circuits here - no need to check the second 'd')
- Not all characters valid → Not Consistent (count = 3)
Final Answer: 3
The code sum(all(c in s for c in w) for w in words)
evaluates to:
- "a": all(['a' in s]) = True → 1
- "b": all(['b' in s]) = True → 1
- "cb": all(['c' in s, 'b' in s]) = True → 1
- "abd": all(['a' in s, 'b' in s, 'd' in s]) = False → 0
- "dd": all(['d' in s, 'd' in s]) = False → 0
Sum = 1 + 1 + 1 + 0 + 0 = 3
Solution Implementation
1class Solution:
2 def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
3 # Convert allowed string to a set for O(1) lookup
4 allowed_chars = set(allowed)
5
6 # Count words that are consistent (all characters are in allowed set)
7 consistent_count = 0
8
9 # Check each word in the words list
10 for word in words:
11 # Check if all characters in the word are allowed
12 is_consistent = True
13 for char in word:
14 if char not in allowed_chars:
15 is_consistent = False
16 break
17
18 # Increment count if word is consistent
19 if is_consistent:
20 consistent_count += 1
21
22 return consistent_count
23```
24
25Alternative concise version maintaining the same logic as original:
26
27```python3
28class Solution:
29 def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
30 # Create set of allowed characters for efficient lookup
31 allowed_set = set(allowed)
32
33 # Count words where all characters exist in allowed_set
34 # Using generator expression with all() to check each word
35 return sum(all(char in allowed_set for char in word) for word in words)
36
1class Solution {
2 /**
3 * Counts the number of consistent strings in the words array.
4 * A string is consistent if all characters in the string appear in the allowed string.
5 *
6 * @param allowed A string containing all allowed characters
7 * @param words An array of strings to check for consistency
8 * @return The count of consistent strings
9 */
10 public int countConsistentStrings(String allowed, String[] words) {
11 // Create a boolean array to mark allowed characters (26 lowercase letters)
12 boolean[] allowedChars = new boolean[26];
13
14 // Mark all characters from 'allowed' string as true in the array
15 for (char ch : allowed.toCharArray()) {
16 allowedChars[ch - 'a'] = true;
17 }
18
19 // Counter for consistent strings
20 int consistentCount = 0;
21
22 // Check each word in the words array
23 for (String word : words) {
24 // If the word is consistent, increment the counter
25 if (isConsistent(word, allowedChars)) {
26 consistentCount++;
27 }
28 }
29
30 return consistentCount;
31 }
32
33 /**
34 * Checks if a word is consistent with the allowed characters.
35 * A word is consistent if all its characters are present in the allowed set.
36 *
37 * @param word The word to check
38 * @param allowedChars Boolean array indicating which characters are allowed
39 * @return true if the word is consistent, false otherwise
40 */
41 private boolean isConsistent(String word, boolean[] allowedChars) {
42 // Check each character in the word
43 for (int i = 0; i < word.length(); i++) {
44 // If any character is not allowed, the word is not consistent
45 if (!allowedChars[word.charAt(i) - 'a']) {
46 return false;
47 }
48 }
49 // All characters are allowed, so the word is consistent
50 return true;
51 }
52}
53
1class Solution {
2public:
3 int countConsistentStrings(string allowed, vector<string>& words) {
4 // Create a bitset to mark which characters are allowed
5 // Each bit position represents a letter (0='a', 1='b', ..., 25='z')
6 bitset<26> allowedChars;
7
8 // Mark all characters in 'allowed' string as valid
9 for (const char& ch : allowed) {
10 allowedChars[ch - 'a'] = 1;
11 }
12
13 // Counter for consistent strings
14 int consistentCount = 0;
15
16 // Lambda function to check if a word is consistent
17 // A word is consistent if all its characters are in the allowed set
18 auto isConsistent = [&](const string& word) -> bool {
19 for (const char& ch : word) {
20 // If character is not in allowed set, word is not consistent
21 if (!allowedChars[ch - 'a']) {
22 return false;
23 }
24 }
25 return true;
26 };
27
28 // Check each word and count consistent ones
29 for (const string& word : words) {
30 if (isConsistent(word)) {
31 consistentCount++;
32 }
33 }
34
35 return consistentCount;
36 }
37};
38
1/**
2 * Counts the number of consistent strings in the words array.
3 * A string is consistent if all characters in the string appear in the allowed string.
4 *
5 * @param allowed - String containing allowed characters
6 * @param words - Array of strings to check for consistency
7 * @returns Number of consistent strings
8 */
9function countConsistentStrings(allowed: string, words: string[]): number {
10 // Create a set of allowed characters for O(1) lookup
11 const allowedCharSet = new Set<string>([...allowed]);
12
13 // Get total number of words
14 const totalWords = words.length;
15
16 // Initialize count with total words (assume all are consistent)
17 let consistentCount = totalWords;
18
19 // Check each word for consistency
20 for (const word of words) {
21 // Check each character in the current word
22 for (const char of word) {
23 // If character is not in allowed set, word is inconsistent
24 if (!allowedCharSet.has(char)) {
25 consistentCount--;
26 break; // No need to check remaining characters
27 }
28 }
29 }
30
31 return consistentCount;
32}
33
Time and Space Complexity
Time Complexity: O(m)
, where m
is the total length of all strings in the words
list.
The algorithm works as follows:
- Creating a set from
allowed
takesO(|allowed|)
time - For each word
w
inwords
, we check if all charactersc
inw
are in the sets
- Checking if a character is in a set takes
O(1)
time on average - The total number of character checks across all words equals the sum of lengths of all words, which is
m
- Therefore, the overall time complexity is
O(|allowed| + m)
, which simplifies toO(m)
since typicallym >> |allowed|
Space Complexity: O(C)
, where C
is the size of the character set in allowed
.
The space analysis:
- We create a set
s
from theallowed
string, which stores at mostC
unique characters - In this problem, since
allowed
contains only lowercase English letters,C ≤ 26
- The generator expression and
sum()
function useO(1)
additional space - Therefore, the space complexity is
O(C)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using String Instead of Set for Lookups
A frequent mistake is checking character membership directly in the allowed
string rather than converting it to a set first.
Incorrect approach:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
# Wrong: Using string for membership checks
return sum(all(c in allowed for c in w) for w in words)
Why it's problematic:
- Checking
c in allowed
on a string has O(m) time complexity for each lookup - With multiple words and characters, this becomes O(n × k × m) instead of O(n × k)
- For large
allowed
strings, this significantly impacts performance
Correct approach:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
# Right: Convert to set first
allowed_set = set(allowed)
return sum(all(c in allowed_set for c in w) for w in words)
2. Not Handling Empty Strings or Edge Cases
While the problem likely guarantees non-empty inputs, defensive programming suggests checking edge cases.
Potential issue:
# Might fail with empty allowed string or None values
allowed_set = set(allowed) # What if allowed is None or empty?
Robust solution:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
if not allowed or not words:
return 0 if not allowed else len([w for w in words if w == ""])
allowed_set = set(allowed)
return sum(all(c in allowed_set for c in w) for w in words)
3. Using Set Operations Incorrectly
Some attempt to use set operations but apply them incorrectly.
Common mistake:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
allowed_set = set(allowed)
count = 0
for word in words:
# Wrong: This checks if sets are equal, not if word chars are subset
if set(word) == allowed_set:
count += 1
return count
Correct set-based approach:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
allowed_set = set(allowed)
count = 0
for word in words:
# Right: Check if word's characters are subset of allowed
if set(word).issubset(allowed_set):
count += 1
return count
4. Misunderstanding the Problem Requirements
A critical misread is thinking we need to use ALL allowed characters, rather than ensuring words ONLY contain allowed characters.
Wrong interpretation:
# Incorrectly checking if word contains all allowed characters
if all(c in word for c in allowed): # Wrong direction!
Correct interpretation:
# Correctly checking if all word characters are in allowed
if all(c in allowed_set for c in word): # Right direction!
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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!