Frequency Counter Pattern
The Frequency Counter pattern uses objects or sets to collect values/frequencies of values. This pattern can often avoid the need for nested loops or O(n²) operations with arrays/strings.
What is Frequency Counter?
This pattern uses objects or sets to collect values and their frequencies. It allows us to avoid nested loops or quadratic time operations when comparing pieces of data.
When to Use?
- Comparing two arrays or strings to check if they're anagrams
- Finding if a subset of elements exists in another array
- Checking if elements in one array are squares of another array
- Counting occurrences of elements
Common Applications
- Anagram validation
- Pattern matching
- Frequency analysis
- Character counting
- Array comparison
Time Complexity
Most frequency counter solutions achieve O(n) time complexity, compared to O(n²) with nested loops.
Example Problems
1. Valid Anagram
Problem Description
Write a function to determine if the second string is an anagram of the first string.
Example
Input: 'anagram', 'nagaram';
Output: true;
Input: 'rat', 'car';
Output: false;
Solution
function validAnagram(str1, str2) {
if (str1.length !== str2.length) return false;
const frequencyCounter = {};
// Count frequencies of first string
for (let char of str1) {
frequencyCounter[char] = (frequencyCounter[char] || 0) + 1;
}
// Compare with second string
for (let char of str2) {
if (!frequencyCounter[char]) {
return false;
}
frequencyCounter[char]--;
}
return true;
}
Time Complexity
- Time: O(n)
- Space: O(n)
2. Same Squared
Problem Description
Write a function that accepts two arrays. The function should return true if every value in the first array has its corresponding value squared in the second array.
Example
Input: [1, 2, 3], [4, 1, 9];
Output: true;
Input: [1, 2, 3], [1, 9];
Output: false;
Solution
function sameSquared(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
const frequencyCounter1 = {};
const frequencyCounter2 = {};
// Count frequencies of both arrays
for (let val of arr1) {
frequencyCounter1[val] = (frequencyCounter1[val] || 0) + 1;
}
for (let val of arr2) {
frequencyCounter2[val] = (frequencyCounter2[val] || 0) + 1;
}
// Compare frequencies
for (let key in frequencyCounter1) {
if (!(key ** 2 in frequencyCounter2)) {
return false;
}
if (frequencyCounter2[key ** 2] !== frequencyCounter1[key]) {
return false;
}
}
return true;
}
Time Complexity
- Time: O(n)
- Space: O(n)
Practice Problems
To master the frequency counter pattern, try solving these problems:
- Find all duplicates in an array
- First non-repeating character
- Count unique values in sorted array
- Check if two strings are permutations
- Most frequent element in array
Common Frequency Counter Strategies
-
Using Objects
- Create object to store frequencies
- Compare frequencies between objects
-
Using Maps
- Use Map for non-string keys
- Better for complex key types
-
Using Sets
- For unique value tracking
- Quick lookup operations
Tips for Solving Frequency Counter Problems
- Consider edge cases (empty inputs, different lengths)
- Choose appropriate data structure (Object vs Map vs Set)
- Clear variable names for frequency counters
- Handle case sensitivity for strings
- Consider space-time tradeoffs
Common Pitfalls
-
Forgetting to Check Lengths
- Always compare lengths first for matching problems
-
Not Handling Edge Cases
- Empty inputs
- Special characters
- Case sensitivity