1002. Find Common Characters
Problem Description
You are given an array of strings called words
. Your task is to find all characters that appear in every single string within this array. If a character appears multiple times in all strings, you need to include it that many times in your result.
For example, if the character 'l' appears at least twice in every string, then 'l' should appear twice in your output. The key point is that you're looking for the common characters across all strings, considering their minimum frequency.
The solution uses a counting approach where it:
- First counts the frequency of each character in the first word using
Counter(words[0])
- Then for each subsequent word, it counts that word's character frequencies
- For each character in the running count, it updates the count to be the minimum between the current count and the new word's count - this ensures we only keep characters that appear in all words with their minimum frequency
- Finally, it uses
cnt.elements()
to expand the counter back into a list of characters, where each character appears according to its count
The time complexity is O(n * m)
where n
is the number of words and m
is the average length of each word, since we need to process each character in each word.
Intuition
When we need to find common elements across multiple sets, the key insight is to think about intersection. For characters that appear in all strings, we need to track not just their presence but also their frequency.
Consider this: if the letter 'a' appears 3 times in the first string, 2 times in the second string, and 5 times in the third string, then 'a' can only be common to all strings at most 2 times (the minimum frequency). This is because we can't claim 'a' appears 3 times in all strings when the second string only has 2 'a's.
This leads us to the core idea: for each character, we need to find its minimum frequency across all strings. If a character doesn't appear in even one string, its minimum frequency becomes 0, effectively removing it from our result.
We can start by counting characters in the first string as our initial reference. Then, as we examine each subsequent string, we update our counts by taking the minimum between what we currently have and what the new string offers. This process is like repeatedly finding the intersection of character frequencies - we keep narrowing down to only what's truly common.
The beauty of this approach is that by using the minimum operation, we automatically handle both cases: characters that don't appear in all strings (their count becomes 0) and characters that appear with different frequencies (we keep the smallest count that works for all strings).
Solution Approach
The solution uses a counting approach with Python's Counter
data structure to efficiently track character frequencies.
Step 1: Initialize the counter
cnt = Counter(words[0])
We start by creating a Counter
object from the first word. This gives us a dictionary-like structure where keys are characters and values are their frequencies in the first word. For example, if words[0] = "bella"
, then cnt = {'b': 1, 'e': 1, 'l': 2, 'a': 1}
.
Step 2: Process remaining words
for w in words:
t = Counter(w)
for c in cnt:
cnt[c] = min(cnt[c], t[c])
For each subsequent word in the array:
- Create a temporary counter
t
for the current word - For each character
c
that exists in our running countercnt
, update its count to be the minimum between the current count and the count in wordw
- Note that
t[c]
returns 0 if characterc
doesn't exist in the current word, which effectively setscnt[c]
to 0
This process ensures that:
- Characters not present in all words will have their count reduced to 0
- Characters present in all words will retain the minimum frequency across all words
Step 3: Generate the result
return list(cnt.elements())
The elements()
method of Counter returns an iterator that repeats each character according to its count. Converting this to a list gives us our final answer where each common character appears the correct number of times.
For example, if cnt = {'l': 2, 'e': 1}
after processing all words, then list(cnt.elements())
returns ['l', 'l', 'e']
.
The time complexity is O(n * m)
where n
is the number of words and m
is the average length of each word. The space complexity is O(1)
since we only track at most 26 lowercase English letters.
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:
Input: words = ["bella", "label", "roller"]
Step 1: Initialize with first word
- Start with
words[0] = "bella"
- Create counter:
cnt = {'b': 1, 'e': 1, 'l': 2, 'a': 1}
Step 2: Process "label"
- Create counter for "label":
t = {'l': 2, 'a': 1, 'b': 1, 'e': 1}
- Update
cnt
by taking minimum frequencies:- 'b': min(1, 1) = 1
- 'e': min(1, 1) = 1
- 'l': min(2, 2) = 2
- 'a': min(1, 1) = 1
- After processing:
cnt = {'b': 1, 'e': 1, 'l': 2, 'a': 1}
Step 3: Process "roller"
- Create counter for "roller":
t = {'r': 2, 'o': 1, 'l': 2, 'e': 1}
- Update
cnt
by taking minimum frequencies:- 'b': min(1, 0) = 0 (no 'b' in "roller")
- 'e': min(1, 1) = 1
- 'l': min(2, 2) = 2
- 'a': min(1, 0) = 0 (no 'a' in "roller")
- After processing:
cnt = {'b': 0, 'e': 1, 'l': 2, 'a': 0}
Step 4: Generate result
- Call
cnt.elements()
which yields each character based on its count - Characters with count 0 ('b' and 'a') are not included
- 'e' appears once, 'l' appears twice
- Result:
['e', 'l', 'l']
The output correctly shows that only 'e' and 'l' appear in all three words, with 'l' appearing at least twice in each word, so it appears twice in the result.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def commonChars(self, words: List[str]) -> List[str]:
6 # Initialize counter with character frequencies from the first word
7 char_count = Counter(words[0])
8
9 # Iterate through remaining words to find common characters
10 for word in words[1:]:
11 # Count character frequencies in current word
12 current_word_count = Counter(word)
13
14 # Update char_count to keep minimum frequency for each character
15 for char in char_count:
16 char_count[char] = min(char_count[char], current_word_count[char])
17
18 # Convert counter to list with characters repeated by their frequencies
19 # elements() returns an iterator over elements repeating each as many times as its count
20 return list(char_count.elements())
21
1class Solution {
2 public List<String> commonChars(String[] words) {
3 // Initialize frequency array for 26 lowercase letters
4 // Set initial values to a large number (acts as infinity)
5 int[] minFrequency = new int[26];
6 Arrays.fill(minFrequency, 20000);
7
8 // Process each word to find minimum frequency of each character
9 for (String word : words) {
10 // Count character frequencies in current word
11 int[] currentWordFrequency = new int[26];
12 for (int i = 0; i < word.length(); i++) {
13 currentWordFrequency[word.charAt(i) - 'a']++;
14 }
15
16 // Update minimum frequency for each character
17 for (int i = 0; i < 26; i++) {
18 minFrequency[i] = Math.min(minFrequency[i], currentWordFrequency[i]);
19 }
20 }
21
22 // Build result list with characters appearing in all words
23 List<String> result = new ArrayList<>();
24 for (int i = 0; i < 26; i++) {
25 // Add each character the minimum number of times it appears in all words
26 result.addAll(Collections.nCopies(minFrequency[i], String.valueOf((char) ('a' + i))));
27 }
28
29 return result;
30 }
31}
32
1class Solution {
2public:
3 vector<string> commonChars(vector<string>& words) {
4 // Initialize frequency array for 26 letters with a large value
5 // This will store the minimum frequency of each character across all words
6 vector<int> minFrequency(26, 20000);
7
8 // Process each word to find minimum character frequencies
9 for (const auto& word : words) {
10 // Count frequency of each character in current word
11 vector<int> currentFrequency(26, 0);
12 for (char ch : word) {
13 currentFrequency[ch - 'a']++;
14 }
15
16 // Update minimum frequency for each character
17 for (int i = 0; i < 26; i++) {
18 minFrequency[i] = min(minFrequency[i], currentFrequency[i]);
19 }
20 }
21
22 // Build result vector with common characters
23 vector<string> result;
24 for (int i = 0; i < 26; i++) {
25 // Add character to result based on its minimum frequency
26 for (int j = 0; j < minFrequency[i]; j++) {
27 result.push_back(string(1, 'a' + i));
28 }
29 }
30
31 return result;
32 }
33};
34
1/**
2 * Find common characters that appear in all strings including duplicates
3 * @param words - Array of strings to find common characters from
4 * @returns Array of common characters including duplicates
5 */
6function commonChars(words: string[]): string[] {
7 // Initialize frequency counter for 26 lowercase letters with a large value
8 // This will track the minimum occurrence of each character across all words
9 const minFrequency: number[] = Array(26).fill(20000);
10
11 // ASCII code for 'a' to calculate character indices
12 const ASCII_CODE_A: number = 'a'.charCodeAt(0);
13
14 // Process each word to find minimum character frequencies
15 for (const word of words) {
16 // Count character frequencies in the current word
17 const currentWordFrequency: number[] = Array(26).fill(0);
18
19 // Count each character in the current word
20 for (const char of word) {
21 const charIndex: number = char.charCodeAt(0) - ASCII_CODE_A;
22 currentWordFrequency[charIndex]++;
23 }
24
25 // Update minimum frequencies by comparing with current word
26 for (let i = 0; i < 26; i++) {
27 minFrequency[i] = Math.min(minFrequency[i], currentWordFrequency[i]);
28 }
29 }
30
31 // Build result array with common characters
32 const result: string[] = [];
33
34 // Add characters to result based on their minimum frequency
35 for (let i = 0; i < 26; i++) {
36 if (minFrequency[i] > 0) {
37 // Convert index back to character and add it minFrequency[i] times
38 const character: string = String.fromCharCode(i + ASCII_CODE_A);
39 const repeatedChars: string = character.repeat(minFrequency[i]);
40 result.push(...repeatedChars);
41 }
42 }
43
44 return result;
45}
46
Time and Space Complexity
Time Complexity: O(n × m)
where n
is the number of words in the array and m
is the average length of each word. More precisely, it can be written as O(n × Σwᵢ)
where Σwᵢ
represents the sum of all word lengths.
- Creating the initial Counter for
words[0]
takesO(w₀)
time wherew₀
is the length of the first word - The loop iterates through
n-1
remaining words - For each word, creating a Counter takes
O(wᵢ)
time - The inner loop iterates through at most
26
characters (the size of the character set|Σ|
) incnt
, and eachmin
operation isO(1)
- Converting the Counter to a list using
elements()
takesO(k)
wherek
is the total count of common characters, which is bounded by the length of the shortest word
Therefore, the total time complexity is O(w₀ + Σᵢ₌₁ⁿ⁻¹(wᵢ + |Σ|) + k)
which simplifies to O(n × Σwᵢ)
since the character set size |Σ| = 26
is constant.
Space Complexity: O(|Σ|)
where |Σ|
is the size of the character set, which equals 26
for lowercase English letters.
- The Counter
cnt
stores at most26
unique characters (lowercase English letters) - The temporary Counter
t
also stores at most26
unique characters - The output list size depends on the result but is bounded by the input
Since the character set size is constant at 26
, the space complexity is O(1)
in practical terms, but more formally expressed as O(|Σ|)
or O(26)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Modifying the Counter While Iterating
A common mistake is trying to remove characters from the counter while iterating over it:
Incorrect Approach:
for w in words[1:]:
t = Counter(w)
for c in cnt:
if c not in t:
del cnt[c] # RuntimeError: dictionary changed size during iteration
else:
cnt[c] = min(cnt[c], t[c])
Why it fails: Modifying a dictionary (or Counter) while iterating over it causes a RuntimeError in Python.
Correct Solution: Let the minimum operation handle it naturally. When a character doesn't exist in the current word, t[c]
returns 0, so min(cnt[c], t[c])
becomes 0, effectively removing that character from the final result.
Pitfall 2: Not Handling Empty Word Arrays
If the input array is empty, accessing words[0]
will raise an IndexError:
Incorrect Approach:
def commonChars(self, words: List[str]) -> List[str]:
cnt = Counter(words[0]) # IndexError if words is empty
# ...
Correct Solution: Add a guard clause:
def commonChars(self, words: List[str]) -> List[str]:
if not words:
return []
cnt = Counter(words[0])
# ...
Pitfall 3: Using Set Intersection Instead of Frequency Counting
Some might try to use set operations, which loses frequency information:
Incorrect Approach:
def commonChars(self, words: List[str]) -> List[str]:
common = set(words[0])
for w in words[1:]:
common &= set(w) # This loses frequency information
return list(common) # Wrong: returns ['l', 'e'] instead of ['l', 'l', 'e']
Why it fails: Sets only track unique elements, not their frequencies. If 'l' appears twice in all words, the set approach would only return one 'l'.
Correct Solution: Use Counter to maintain frequency information throughout the process.
Pitfall 4: Creating New Counter in Each Iteration
Creating a new counter for cnt
in each iteration instead of updating it in-place:
Inefficient Approach:
for w in words[1:]:
t = Counter(w)
new_cnt = Counter()
for c in cnt:
new_cnt[c] = min(cnt[c], t[c])
cnt = new_cnt # Creates unnecessary new objects
Better Solution: Update the existing counter in-place to avoid unnecessary object creation and improve performance.
Which type of traversal does breadth first search do?
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!