Character Counting
Overview
Character counting is a fundamental technique in string algorithms where you track how many times each character appears in a string. This simple concept forms the basis for solving many complex string problems efficiently. Character counting problems test your understanding of hash maps, arrays, sliding windows, and frequency analysis.
Character counting is used in:
- Finding first/last unique characters
- Validating character frequencies
- Counting distinct characters
- Character replacement problems
- Frequency-based transformations
- Pattern matching with frequency constraints
Data Structures for Character Counting
Option 1: Array (Fixed Alphabet)
For lowercase letters (a-z), use an array of size 26. For ASCII, use size 128 or 256.
function countFrequencyArray(s: string): number[] {
const freq = new Array(26).fill(0);
for (const char of s) {
freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
return freq;
}Advantages:
- O(1) access time
- Memory efficient for fixed alphabets
- Faster than Map for small alphabets
Use when: Alphabet size is small and fixed (e.g., lowercase letters a-z)
Option 2: Map/Object (Dynamic)
Use a Map or object when the alphabet is unknown or large.
function countFrequencyMap(s: string): Map<string, number> {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
return freq;
}Advantages:
- Works for any characters (Unicode-friendly)
- Dynamic size
- No need to know alphabet size
Use when: Alphabet size is unknown or characters are arbitrary
Option 3: Object/Record (JavaScript)
function countFrequencyObject(s: string): Record<string, number> {
const freq: Record<string, number> = {};
for (const char of s) {
freq[char] = (freq[char] || 0) + 1;
}
return freq;
}Advantages:
- Simple syntax
- Good for small to medium strings
Use when: You prefer object syntax and don't need Map-specific features
Pattern 1: First Non-Repeating Character
Find the first character that appears exactly once.
Approach: Count frequencies, then find first character with count 1.
function firstUniqChar(s: string): number {
const freq = new Map<string, number>();
// Count frequencies
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
// Find first character with count 1
for (let i = 0; i < s.length; i++) {
if (freq.get(s[i]) === 1) {
return i;
}
}
return -1;
}Optimized with array:
function firstUniqCharArray(s: string): number {
const freq = new Array(26).fill(0);
// Count frequencies
for (const char of s) {
freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
// Find first character with count 1
for (let i = 0; i < s.length; i++) {
if (freq[s.charCodeAt(i) - 'a'.charCodeAt(0)] === 1) {
return i;
}
}
return -1;
}Time: O(n), Space: O(k) where k is alphabet size
Variation: Find first non-repeating character (return character, not index):
function firstUniqCharValue(s: string): string {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
for (const char of s) {
if (freq.get(char) === 1) {
return char;
}
}
return '';
}Pattern 2: Last Non-Repeating Character
Find the last character that appears exactly once.
function lastUniqChar(s: string): number {
const freq = new Map<string, number>();
// Count frequencies
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
// Find last character with count 1
for (let i = s.length - 1; i >= 0; i--) {
if (freq.get(s[i]) === 1) {
return i;
}
}
return -1;
}Time: O(n), Space: O(k)
Pattern 3: Count Distinct Characters
Count how many unique characters appear in a string.
Approach 1: Using Set
function countDistinct(s: string): number {
return new Set(s).size;
}Approach 2: Using Frequency Map
function countDistinctFreq(s: string): number {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
return freq.size;
}Time: O(n), Space: O(k)
Variation: Count distinct characters in a substring (sliding window):
function countDistinctInWindow(s: string, k: number): number[] {
const results: number[] = [];
const freq = new Map<string, number>();
// Initialize first window
for (let i = 0; i < k; i++) {
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
}
results.push(freq.size);
// Slide window
for (let i = k; i < s.length; i++) {
// Remove left character
const leftChar = s[i - k];
const count = freq.get(leftChar)!;
if (count === 1) {
freq.delete(leftChar);
} else {
freq.set(leftChar, count - 1);
}
// Add right character
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
results.push(freq.size);
}
return results;
}Pattern 4: Most Frequent Character
Find the character that appears most frequently.
function mostFrequentChar(s: string): string {
const freq = new Map<string, number>();
// Count frequencies
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
// Find maximum
let maxCount = 0;
let maxChar = '';
for (const [char, count] of freq) {
if (count > maxCount) {
maxCount = count;
maxChar = char;
}
}
return maxChar;
}Return all characters with maximum frequency:
function mostFrequentChars(s: string): string[] {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
const maxCount = Math.max(...Array.from(freq.values()));
const result: string[] = [];
for (const [char, count] of freq) {
if (count === maxCount) {
result.push(char);
}
}
return result;
}Time: O(n), Space: O(k)
Pattern 5: Characters with Specific Frequency
Find all characters that appear exactly K times.
function charsWithFrequency(s: string, k: number): string[] {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
const result: string[] = [];
for (const [char, count] of freq) {
if (count === k) {
result.push(char);
}
}
return result;
}Find characters appearing at least K times:
function charsWithMinFrequency(s: string, k: number): string[] {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
const result: string[] = [];
for (const [char, count] of freq) {
if (count >= k) {
result.push(char);
}
}
return result;
}Find characters appearing at most K times:
function charsWithMaxFrequency(s: string, k: number): string[] {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
const result: string[] = [];
for (const [char, count] of freq) {
if (count <= k) {
result.push(char);
}
}
return result;
}Pattern 6: Character Frequency Distribution
Get a sorted list of characters by frequency (most frequent first).
function charsByFrequency(s: string): Array<[string, number]> {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
return Array.from(freq.entries()).sort((a, b) => b[1] - a[1]);
}Sort by character if frequencies are equal:
function charsByFrequencySorted(s: string): Array<[string, number]> {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
return Array.from(freq.entries()).sort((a, b) => {
if (b[1] !== a[1]) {
return b[1] - a[1]; // Sort by frequency descending
}
return a[0].localeCompare(b[0]); // Then by character ascending
});
}Time: O(n + k log k) where k is distinct characters
Pattern 7: Reorganize String by Frequency
Rearrange string so no two same characters are adjacent, prioritizing most frequent.
function reorganizeString(s: string): string {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
// Check if reorganization is possible
const maxCount = Math.max(...Array.from(freq.values()));
if (maxCount > Math.ceil(s.length / 2)) {
return ''; // Impossible
}
// Sort by frequency
const sorted = Array.from(freq.entries()).sort((a, b) => b[1] - a[1]);
const result: string[] = new Array(s.length);
let index = 0;
// Fill even positions first, then odd
for (const [char, count] of sorted) {
for (let i = 0; i < count; i++) {
if (index >= s.length) index = 1; // Switch to odd positions
result[index] = char;
index += 2;
}
}
return result.join('');
}Time: O(n + k log k), Space: O(n)
Pattern 8: Character Replacement Based on Frequency
Replace characters to maximize frequency of a target character.
function maxFrequencyAfterReplacement(s: string, k: number, targetChar: string): number {
const freq = new Map<string, number>();
let left = 0;
let maxCount = 0; // Count of targetChar in current window
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
const char = s[right];
freq.set(char, (freq.get(char) || 0) + 1);
if (char === targetChar) {
maxCount = Math.max(maxCount, freq.get(char)!);
}
// Shrink if replacements needed exceed k
// Window size - maxCount = characters to replace
while (right - left + 1 - maxCount > k) {
freq.set(s[left], freq.get(s[left])! - 1);
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}Time: O(n), Space: O(k)
Pattern 9: Validate Character Frequency Constraints
Check if string satisfies frequency constraints.
All characters appear at least K times:
function allCharsAtLeastK(s: string, k: number): boolean {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
for (const count of freq.values()) {
if (count < k) {
return false;
}
}
return true;
}At most one character appears odd times (palindrome check):
function canFormPalindrome(s: string): boolean {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
let oddCount = 0;
for (const count of freq.values()) {
if (count % 2 === 1) {
oddCount++;
}
}
return oddCount <= 1;
}All characters appear even times:
function allCharsEven(s: string): boolean {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
for (const count of freq.values()) {
if (count % 2 !== 0) {
return false;
}
}
return true;
}Pattern 10: Character Frequency in Sliding Window
Track character frequencies in a sliding window.
function slidingWindowFrequencies(s: string, k: number): Map<string, number>[] {
const results: Map<string, number>[] = [];
const freq = new Map<string, number>();
// Initialize first window
for (let i = 0; i < k; i++) {
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
}
results.push(new Map(freq));
// Slide window
for (let i = k; i < s.length; i++) {
// Remove left
const leftChar = s[i - k];
const leftCount = freq.get(leftChar)!;
if (leftCount === 1) {
freq.delete(leftChar);
} else {
freq.set(leftChar, leftCount - 1);
}
// Add right
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
results.push(new Map(freq));
}
return results;
}Time: O(n k), Space: O(n k)
Pattern 11: Character Frequency Comparison
Compare character frequencies between two strings.
function compareFrequencies(s1: string, s2: string): boolean {
const freq1 = new Map<string, number>();
const freq2 = new Map<string, number>();
for (const char of s1) {
freq1.set(char, (freq1.get(char) || 0) + 1);
}
for (const char of s2) {
freq2.set(char, (freq2.get(char) || 0) + 1);
}
if (freq1.size !== freq2.size) return false;
for (const [char, count] of freq1) {
if (freq2.get(char) !== count) {
return false;
}
}
return true;
}Find frequency difference:
function frequencyDifference(s1: string, s2: string): Map<string, number> {
const diff = new Map<string, number>();
// Count s1
for (const char of s1) {
diff.set(char, (diff.get(char) || 0) + 1);
}
// Subtract s2
for (const char of s2) {
diff.set(char, (diff.get(char) || 0) - 1);
}
// Remove zeros
for (const [char, count] of diff) {
if (count === 0) {
diff.delete(char);
}
}
return diff;
}Pattern 12: Character Frequency Statistics
Get comprehensive statistics about character frequencies.
interface FrequencyStats {
total: number;
distinct: number;
mostFrequent: string;
leastFrequent: string;
averageFrequency: number;
medianFrequency: number;
}
function getFrequencyStats(s: string): FrequencyStats {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
const frequencies = Array.from(freq.values()).sort((a, b) => a - b);
const total = s.length;
const distinct = freq.size;
let mostFrequent = '';
let maxCount = 0;
let leastFrequent = '';
let minCount = Infinity;
for (const [char, count] of freq) {
if (count > maxCount) {
maxCount = count;
mostFrequent = char;
}
if (count < minCount) {
minCount = count;
leastFrequent = char;
}
}
const averageFrequency = total / distinct;
const medianFrequency = frequencies.length % 2 === 0
? (frequencies[frequencies.length / 2 - 1] + frequencies[frequencies.length / 2]) / 2
: frequencies[Math.floor(frequencies.length / 2)];
return {
total,
distinct,
mostFrequent,
leastFrequent,
averageFrequency,
medianFrequency
};
}When to Use Character Counting
Use character counting when:
- Problem asks about "frequency", "count", or "occurrence"
- Need to find "first unique" or "non-repeating" characters
- Validating character frequency constraints
- Comparing character distributions
- Sliding window problems requiring frequency tracking
- Anagram or permutation problems
Template (Basic Counting)
function countTemplate(s: string): Map<string, number> {
const freq = new Map<string, number>();
for (const char of s) {
freq.set(char, (freq.get(char) || 0) + 1);
}
return freq;
}Template (Array for Fixed Alphabet)
function countArrayTemplate(s: string): number[] {
const freq = new Array(26).fill(0);
for (const char of s) {
freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
return freq;
}Template (Sliding Window with Frequency)
function slidingWindowCountTemplate(s: string, k: number): void {
const freq = new Map<string, number>();
// Initialize window
for (let i = 0; i < k; i++) {
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
}
// Process first window
// ...
// Slide window
for (let i = k; i < s.length; i++) {
// Remove left
const leftCount = freq.get(s[i - k])!;
if (leftCount === 1) freq.delete(s[i - k]);
else freq.set(s[i - k], leftCount - 1);
// Add right
freq.set(s[i], (freq.get(s[i]) || 0) + 1);
// Process window
// ...
}
}Time and Space Complexity Summary
- Basic counting: O(n) time, O(k) space where k is alphabet size
- Array counting: O(n) time, O(1) space for fixed alphabet
- Sliding window: O(n) time, O(k) space
- Sorting frequencies: O(n + k log k) time, O(k) space
Practice Tips
- Choose the right structure — Array for fixed alphabet (a-z), Map for dynamic
- Optimize space — Use array when alphabet size is small and known
- Handle edge cases — Empty strings, single character, all same characters
- Combine with sliding window — For substring frequency problems
- Use frequency for validation — Check constraints efficiently
- Consider Unicode — Use Map for Unicode characters
Common Mistakes
- Forgetting to initialize — Always initialize frequency structures
- Off-by-one errors — Be careful with array indices
- Not handling empty strings — Check length before processing
- Case sensitivity — Normalize if case-insensitive comparison needed
- Unicode handling — Use Map for multi-byte characters
Related Concepts
- Character Frequency Maps — Core technique for counting
- Sliding Window — Often combined with frequency counting
- Hash Maps — Essential data structure for counting
- Anagram Problems — Rely heavily on character counting
- String Validation — Uses frequency constraints
Character counting is a fundamental building block for many string algorithms. Master these patterns, and you'll be able to solve a wide variety of frequency-based problems efficiently. Practice with different data structures and understand when to use each approach.