Facebook Pixel

1684. Count the Number of Consistent Strings

EasyBit ManipulationArrayHash TableStringCounting
Leetcode Link

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" and words = ["ad", "bd", "aaab", "baa", "badab"]
  • "aaab" is consistent because it only contains 'a' and 'b', which are both in allowed
  • "baa" is consistent because it only contains 'a' and 'b', which are both in allowed
  • "ad" is not consistent because it contains 'd', which is not in allowed
  • "bd" is not consistent because it contains 'd', which is not in allowed
  • "badab" is not consistent because it contains 'd', which is not in allowed
  • 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.

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 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:

  1. We need to validate each word individually - this suggests iterating through the words array
  2. For each word, we need to ensure ALL its characters are valid - this suggests checking every character
  3. 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:

  1. 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.

  2. 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 array
    • for c in w: For each word, check every character
    • c in s: Test if character c exists in our set s (O(1) operation)
    • all(...): Returns True only if ALL characters in the word are found in the set
    • sum(...): Counts the number of True 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 returns False 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 Evaluator

Example 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:

  1. Word: "a"

    • Check: Is 'a' in {'a', 'b', 'c'}? → Yes ✓
    • All characters valid → Consistent (count = 1)
  2. Word: "b"

    • Check: Is 'b' in {'a', 'b', 'c'}? → Yes ✓
    • All characters valid → Consistent (count = 2)
  3. Word: "cb"

    • Check: Is 'c' in {'a', 'b', 'c'}? → Yes ✓
    • Check: Is 'b' in {'a', 'b', 'c'}? → Yes ✓
    • All characters valid → Consistent (count = 3)
  4. 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)
  5. 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 takes O(|allowed|) time
  • For each word w in words, we check if all characters c in w are in the set s
  • 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 to O(m) since typically m >> |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 the allowed string, which stores at most C unique characters
  • In this problem, since allowed contains only lowercase English letters, C ≤ 26
  • The generator expression and sum() function use O(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!
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More