DSA Patterns Made Simple · Arrays and Strings: The Six Core Patterns
Counting and Grouping
Count how often things appear, or group items that share a key. Anagrams, most-frequent elements and 'first unique character' all fall to a frequency map.
The problem
Valid Anagram: are "listen" and "silent" made of exactly the same letters? A person would tally the letters of each word and compare the tallies. That's the whole pattern.
How to spot it
- "Same letters / same items, any order" (anagrams, permutations of a string)
- "Most frequent", "top k", "appears more than n/2 times"
- "First unique", "count of each"
- "Group together items that are alike" (group anagrams, group by property)
The idea
Build a frequency map: item → how many times it appears. Comparing two maps answers "same items?", looking for the largest counts answers "most frequent?", and counts of 1 answer "unique?".
For grouping, the map goes from a key that alike items share to a list of those items. For anagrams, the key is the sorted letters: "eat", "tea" and "ate" all become "aet".
The template
from collections import Counter, defaultdict
counts = Counter(items) # item -> count
groups = defaultdict(list) # key -> items sharing that key
for item in items:
groups[key_of(item)].append(item)
const counts = new Map(); // item -> count
for (const item of items) counts.set(item, (counts.get(item) ?? 0) + 1);
const groups = new Map(); // key -> items sharing that key
for (const item of items) {
const key = keyOf(item);
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(item);
}
Worked example
Three problems, one idea: valid anagram (compare counts), first unique character (find count 1), and group anagrams (group by sorted letters).
from collections import Counter, defaultdict
def is_anagram(a, b):
return Counter(a) == Counter(b)
def first_unique_char(s):
counts = Counter(s)
for i, ch in enumerate(s):
if counts[ch] == 1:
return i
return -1
def group_anagrams(words):
groups = defaultdict(list)
for w in words:
groups["".join(sorted(w))].append(w) # "tea" -> "aet"
return list(groups.values())
print("listen/silent:", is_anagram("listen", "silent"))
print("rat/car:", is_anagram("rat", "car"))
print("first unique in 'leetcode':", first_unique_char("leetcode"))
print("first unique in 'aabb':", first_unique_char("aabb"))
for group in group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]):
print(" ".join(group))
Output
listen/silent: True rat/car: False first unique in 'leetcode': 0 first unique in 'aabb': -1 eat tea ate tan nat bat
function counter(s) {
const counts = new Map();
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
return counts;
}
function isAnagram(a, b) {
if (a.length !== b.length) return false;
const ca = counter(a), cb = counter(b);
for (const [ch, n] of ca) if (cb.get(ch) !== n) return false;
return true;
}
function firstUniqueChar(s) {
const counts = counter(s);
for (let i = 0; i < s.length; i++) if (counts.get(s[i]) === 1) return i;
return -1;
}
function groupAnagrams(words) {
const groups = new Map();
for (const w of words) {
const key = [...w].sort().join(""); // "tea" -> "aet"
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(w);
}
return [...groups.values()];
}
const py = (b) => (b ? "True" : "False"); // print booleans the way Python does, so both tabs match
console.log("listen/silent:", py(isAnagram("listen", "silent")));
console.log("rat/car:", py(isAnagram("rat", "car")));
console.log("first unique in 'leetcode':", firstUniqueChar("leetcode"));
console.log("first unique in 'aabb':", firstUniqueChar("aabb"));
for (const group of groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])) {
console.log(group.join(" "));
}
Output
listen/silent: True rat/car: False first unique in 'leetcode': 0 first unique in 'aabb': -1 eat tea ate tan nat bat
Common mistakes
- Sorting both strings to check anagrams works (O(n log n)) but counting is O(n). Mention both; either is usually accepted.
- Comparing JavaScript
Maps with===. Two maps are never===equal; compare entry by entry (or check lengths first, as above). - Picking a weak group key. For anagrams, the key must be the same for all anagrams and different otherwise; sorted letters or a 26-letter count both work.
- Top-k by sorting the whole map is O(n log n); a heap or bucket sort can do better (see the Heaps lesson).
Practice
- Valid Anagram (easy)
- Group Anagrams (medium)
- Top K Frequent Elements (medium): count, then put numbers in buckets by count.
Key points
- Frequency map: item → count. Compare maps, find count 1, or find the largest counts.
- Grouping: key → list of items that share the key.
- Choose a key that is identical for "alike" items (sorted letters for anagrams).
- Counting is O(n) time and O(k) space for k distinct items.
Practice questions
How would you check whether two strings are anagrams in O(n)?
Answer
Count the characters of each (a frequency map or a 26-slot array for lowercase letters) and compare the counts. Different lengths can be rejected immediately.
What key would you use to group anagrams together?
Answer
Any value that is the same for all anagrams of a word: the word's letters sorted ("tea" → "aet"), or a tuple of 26 letter counts, which avoids sorting.
Open in the app to mark lessons complete, take notes and track your progress.