2085. Count Common Words With One Occurrence
Problem Description
You are given two string arrays words1
and words2
. Your task is to count how many strings appear exactly once in words1
AND also appear exactly once in words2
.
In other words, you need to find strings that:
- Occur exactly 1 time in
words1
- AND occur exactly 1 time in
words2
The solution uses two hash tables (Counter
objects) to count the frequency of each string in both arrays. cnt1
stores the count of strings in words1
, and cnt2
stores the count of strings in words2
.
The key logic is in the final line: for each word w
in cnt1
, we check if its count v
equals 1 (appears once in words1
) AND if cnt2[w]
also equals 1 (appears once in words2
). The sum()
function counts how many words satisfy both conditions.
For example:
- If
words1 = ["a", "b", "c", "a"]
andwords2 = ["b", "c", "c"]
"a"
appears 2 times inwords1
(not exactly once) - doesn't count"b"
appears 1 time inwords1
and 1 time inwords2
- counts"c"
appears 1 time inwords1
but 2 times inwords2
- doesn't count- Result: 1 (only
"b"
satisfies the condition)
Intuition
The problem asks us to find strings that are "unique" in both arrays - appearing exactly once in each. The natural approach is to count the frequency of each string in both arrays separately.
Why use hash tables? When we need to track occurrences of elements, hash tables provide an efficient way to store and retrieve counts. Python's Counter
is perfect for this - it automatically counts the frequency of each element in a collection.
The key insight is that we need to check two conditions simultaneously:
- A string appears exactly once in
words1
- The same string appears exactly once in
words2
Instead of trying to find common elements first and then check their counts, we can be more efficient. We iterate through all words in the first hash table and for each word, check if it meets both conditions. This avoids creating intermediate data structures.
The elegance of the solution comes from the fact that cnt2[w]
returns 0 if word w
doesn't exist in words2
. So when we check cnt2[w] == 1
, it automatically handles both requirements: the word must exist in words2
AND appear exactly once. Words that don't exist in words2
will have a count of 0, failing the condition.
This approach has optimal time complexity O(n + m)
where n
and m
are the lengths of the two arrays, as we only need to traverse each array once to build the counters, and then traverse one counter to check conditions.
Solution Approach
The implementation uses hash tables and counting to efficiently solve this problem. Here's how the solution works step by step:
Step 1: Count frequencies in both arrays
cnt1 = Counter(words1) cnt2 = Counter(words2)
We create two Counter
objects (hash tables) to store the frequency of each string. For example, if words1 = ["a", "b", "a"]
, then cnt1 = {"a": 2, "b": 1}
.
Step 2: Iterate and check conditions
sum(v == 1 and cnt2[w] == 1 for w, v in cnt1.items())
This line does multiple things:
cnt1.items()
gives us pairs of(word, count)
from the first array- For each pair
(w, v)
:v == 1
checks if the word appears exactly once inwords1
cnt2[w] == 1
checks if the same word appears exactly once inwords2
- The
and
operator ensures both conditions must be true
- The generator expression produces
True
(1) orFalse
(0) for each word sum()
counts how manyTrue
values we have, giving us the final answer
Why this approach is efficient:
- We only traverse each array once to build the counters:
O(n)
forwords1
andO(m)
forwords2
- We then traverse
cnt1
once to check conditions:O(unique words in words1)
- Each lookup in
cnt2
isO(1)
due to hash table implementation - Overall time complexity:
O(n + m)
wheren
andm
are the lengths of the two arrays - Space complexity:
O(n + m)
for storing the two hash tables
The beauty of this solution lies in its simplicity - by using Counter
objects, we avoid manually tracking frequencies and can directly query counts with clean, readable code.
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 concrete example to understand how the solution works.
Given:
words1 = ["apple", "banana", "apple", "orange"]
words2 = ["banana", "orange", "orange", "grape"]
Step 1: Build frequency counters
First, we count how many times each word appears in both arrays:
cnt1 = Counter(words1) # cnt1 = {"apple": 2, "banana": 1, "orange": 1} cnt2 = Counter(words2) # cnt2 = {"banana": 1, "orange": 2, "grape": 1}
Step 2: Check each word in cnt1
Now we iterate through each word in cnt1
and check if it meets both conditions:
Word | Count in words1 (v) | v == 1? | Count in words2 | cnt2[w] == 1? | Both conditions met? |
---|---|---|---|---|---|
"apple" | 2 | False | 0 | False | False |
"banana" | 1 | True | 1 | True | True ✓ |
"orange" | 1 | True | 2 | False | False |
- "apple": Appears 2 times in
words1
(not exactly once), so it doesn't qualify - "banana": Appears exactly once in
words1
AND exactly once inwords2
- this counts! - "orange": Appears exactly once in
words1
but twice inwords2
, so it doesn't qualify - "grape": Only exists in
words2
, so it's not checked (we only iterate throughcnt1
)
Step 3: Count qualifying words
The expression sum(v == 1 and cnt2[w] == 1 for w, v in cnt1.items())
evaluates to:
- "apple":
False and False
=False
(contributes 0) - "banana":
True and True
=True
(contributes 1) - "orange":
True and False
=False
(contributes 0)
Result: 0 + 1 + 0 = 1
Therefore, only 1 string ("banana") appears exactly once in both arrays.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def countWords(self, words1: List[str], words2: List[str]) -> int:
6 # Count frequency of each word in both lists
7 word_count_1 = Counter(words1)
8 word_count_2 = Counter(words2)
9
10 # Count words that appear exactly once in both lists
11 # Iterate through words in first list and check if:
12 # 1. The word appears exactly once in words1 (v == 1)
13 # 2. The same word appears exactly once in words2 (word_count_2[word] == 1)
14 result = sum(
15 count == 1 and word_count_2[word] == 1
16 for word, count in word_count_1.items()
17 )
18
19 return result
20
1class Solution {
2 public int countWords(String[] words1, String[] words2) {
3 // Create frequency maps to count occurrences of each word in both arrays
4 Map<String, Integer> frequencyMap1 = new HashMap<>();
5 Map<String, Integer> frequencyMap2 = new HashMap<>();
6
7 // Count the frequency of each word in words1
8 for (String word : words1) {
9 frequencyMap1.merge(word, 1, Integer::sum);
10 }
11
12 // Count the frequency of each word in words2
13 for (String word : words2) {
14 frequencyMap2.merge(word, 1, Integer::sum);
15 }
16
17 // Initialize counter for words that appear exactly once in both arrays
18 int count = 0;
19
20 // Iterate through all entries in the first frequency map
21 for (Map.Entry<String, Integer> entry : frequencyMap1.entrySet()) {
22 // Check if the word appears exactly once in words1 AND exactly once in words2
23 if (entry.getValue() == 1 && frequencyMap2.getOrDefault(entry.getKey(), 0) == 1) {
24 count++;
25 }
26 }
27
28 // Return the total count of words appearing exactly once in both arrays
29 return count;
30 }
31}
32
1class Solution {
2public:
3 int countWords(vector<string>& words1, vector<string>& words2) {
4 // Hash map to store frequency of each word in words1
5 unordered_map<string, int> frequencyMap1;
6 // Hash map to store frequency of each word in words2
7 unordered_map<string, int> frequencyMap2;
8
9 // Count occurrences of each word in words1
10 for (const auto& word : words1) {
11 ++frequencyMap1[word];
12 }
13
14 // Count occurrences of each word in words2
15 for (const auto& word : words2) {
16 ++frequencyMap2[word];
17 }
18
19 // Count words that appear exactly once in both arrays
20 int result = 0;
21 for (const auto& [word, frequency] : frequencyMap1) {
22 // Check if current word appears exactly once in words1
23 // and exactly once in words2
24 if (frequency == 1 && frequencyMap2[word] == 1) {
25 ++result;
26 }
27 }
28
29 return result;
30 }
31};
32
1/**
2 * Counts the number of words that appear exactly once in both arrays
3 * @param words1 - First array of words
4 * @param words2 - Second array of words
5 * @returns The count of words appearing exactly once in both arrays
6 */
7function countWords(words1: string[], words2: string[]): number {
8 // Create frequency maps for both word arrays
9 const frequencyMap1 = new Map<string, number>();
10 const frequencyMap2 = new Map<string, number>();
11
12 // Count occurrences of each word in the first array
13 for (const word of words1) {
14 frequencyMap1.set(word, (frequencyMap1.get(word) ?? 0) + 1);
15 }
16
17 // Count occurrences of each word in the second array
18 for (const word of words2) {
19 frequencyMap2.set(word, (frequencyMap2.get(word) ?? 0) + 1);
20 }
21
22 // Count words that appear exactly once in both arrays
23 let count = 0;
24 for (const [word, frequency] of frequencyMap1) {
25 // Check if word appears exactly once in both arrays
26 if (frequency === 1 && frequencyMap2.get(word) === 1) {
27 count++;
28 }
29 }
30
31 return count;
32}
33
Time and Space Complexity
Time Complexity: O(n + m)
The time complexity breaks down as follows:
- Creating
cnt1
usingCounter(words1)
takesO(n)
time, wheren
is the length ofwords1
- Creating
cnt2
usingCounter(words2)
takesO(m)
time, wherem
is the length ofwords2
- The generator expression iterates through all items in
cnt1
, which contains at mostn
unique words, takingO(n)
time - For each word
w
in the iteration, accessingcnt2[w]
takesO(1)
time on average for hash table lookup - The
sum()
function processes the generator expression inO(n)
time
Total time complexity: O(n) + O(m) + O(n) = O(n + m)
Space Complexity: O(n + m)
The space complexity breaks down as follows:
cnt1
stores at mostn
unique words and their counts, requiringO(n)
spacecnt2
stores at mostm
unique words and their counts, requiringO(m)
space- The generator expression and sum operation use
O(1)
additional space
Total space complexity: O(n) + O(m) = O(n + m)
Where n
and m
are the lengths of the arrays words1
and words2
respectively.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Checking Words Not Present in Both Arrays
The Problem:
A common mistake is forgetting that when you access word_count_2[word]
for a word that doesn't exist in words2
, Python's Counter
returns 0 (not an error). While this actually works correctly for our problem (since 0 ≠ 1), developers might misunderstand the logic or try to "optimize" by checking existence first.
Incorrect attempt:
# Unnecessarily checking if word exists first
result = sum(
count == 1 and word in word_count_2 and word_count_2[word] == 1
for word, count in word_count_1.items()
)
Why it's a pitfall: The extra word in word_count_2
check is redundant because Counter
objects return 0 for missing keys, and 0 ≠ 1 will correctly exclude these words anyway.
Pitfall 2: Iterating Through Both Dictionaries
The Problem: Some developers might think they need to check words from both directions, leading to duplicate counting or incorrect logic.
Incorrect attempt:
# Wrong: This would count words twice or miss some cases result = 0 for word in word_count_1: if word_count_1[word] == 1 and word_count_2[word] == 1: result += 1 for word in word_count_2: if word_count_2[word] == 1 and word_count_1[word] == 1: result += 1
Why it's wrong: This counts the same qualifying word twice - once when iterating through word_count_1
and again when iterating through word_count_2
.
Pitfall 3: Using Regular Dictionary Instead of Counter
The Problem: Using a regular dictionary requires manual handling of missing keys, making the code more error-prone.
Error-prone attempt:
# Using regular dict - more complex and error-prone
word_count_1 = {}
for word in words1:
word_count_1[word] = word_count_1.get(word, 0) + 1
word_count_2 = {}
for word in words2:
word_count_2[word] = word_count_2.get(word, 0) + 1
# Need to use .get() to avoid KeyError
result = sum(
count == 1 and word_count_2.get(word, 0) == 1
for word, count in word_count_1.items()
)
Solution: Stick with Counter
- it's specifically designed for frequency counting and handles missing keys gracefully by returning 0.
Correct Approach Reminder:
The original solution is already optimal - iterate through one dictionary and check conditions for both. The Counter
class handles missing keys automatically, making the code both clean and correct.
Depth first search is equivalent to which of the tree traversal order?
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!