Beginner: foundations · Hashing, two pointers and sliding window · topic 12 of 61 · 35 min
Counting and grouping with hashing
Explanation
Counting is the most common use of a map
A frequency map stores each distinct item with the number of times it appears. One pass builds it: O(n) time and O(d) space, where d is the number of distinct items. Python gives you three ways to write it.
words = ["upi", "card", "upi", "cash", "upi", "card"]
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts) # Output: {'upi': 3, 'card': 2, 'cash': 1}
from collections import defaultdict, Counter
dd = defaultdict(int) # a missing key starts at int(), which is 0
for w in words:
dd[w] += 1
c = Counter(words)
print(c.most_common(2)) # Output: [('upi', 3), ('card', 2)]
print(c["wallet"]) # Output: 0 (a missing key counts as 0)Counter is a dict made for counting. Reading a missing key gives 0 instead of a KeyError, and most_common(k) returns the k most common items; items with equal counts come in the order they were first seen.
First unique character
To find the first character that appears exactly once, count everything in a first pass, then walk the string again and return the first index whose count is 1. Two passes are still O(n). The map holds one entry per distinct character, so for lowercase English letters it never has more than 26 entries: O(1) extra space in that case.
Grouping: same key, same list
Grouping puts items that share some property into one list. Choose a key function so that items in the same group get the same key, then keep a dict from key to list. For anagrams ("eat", "tea", "ate") two keys work well:
- The sorted word: "".join(sorted(w)) turns all three into "aet". Cost O(m log m) for a word of length m.
- A count tuple: 26 counts, one per letter, stored as a tuple because a list cannot be a key. Cost O(m) per word, better for long words.
from collections import defaultdict
groups = defaultdict(list)
for w in ["eat", "tea", "tan", "ate", "nat", "bat"]:
groups["".join(sorted(w))].append(w)
print(list(groups.values()))
# Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]Top-k frequent, with a tie rule
"Return the k most frequent words" needs a rule for ties, or two correct programs can print different answers. A common rule: higher count first, and for equal counts, alphabetical order. Sorting by the key (−count, word) does both at once. Always ask about ties in an interview.
from collections import Counter
def top_k(words, k):
counts = Counter(words)
ranked = sorted(counts, key=lambda w: (-counts[w], w))
return ranked[:k]
print(top_k(["b", "a", "c", "a", "b", "d"], 2)) # Output: ['a', 'b']A heap can do this in O(d log k) (Module 8), and grouping words into buckets by count can do it in O(n). The sorting version is clear and usually fast enough.
Subarray sum equals k: prefix sums plus a map
Module 2 previewed this pattern; here is why it works. Let running be the sum of all items so far. A subarray that ends at the current item sums to k exactly when some EARLIER running total equals running − k. So walk once, and at each step add the number of earlier prefixes equal to running − k. A dict from "prefix sum" to "times seen" answers that in O(1).
| Item x | running | running − k | Seen before | Count |
|---|---|---|---|---|
| (start) | 0 | — | — | 0 |
| 1 | 1 | −2 | 0 | 0 |
| 2 | 3 | 0 | 1 | 1 |
| −1 | 2 | −1 | 0 | 1 |
| 2 | 4 | 1 | 1 | 2 |
The trace is for nums = [1, 2, −1, 2] and k = 3. The two subarrays found are [1, 2] and [2, −1, 2]. The dict starts as {0: 1} for the empty prefix, which is how a subarray starting at index 0 gets counted.
def subarray_sum_count(nums, k):
seen = {0: 1}
running = count = 0
for x in nums:
running += x
count += seen.get(running - k, 0) # look up first...
seen[running] = seen.get(running, 0) + 1 # ...then store
return count
print(subarray_sum_count([1, 2, -1, 2], 3)) # Output: 2Look up BEFORE storing the current running total. Otherwise, when k = 0, every prefix would match itself and count an empty subarray.
Common mistakes
- counts[w] += 1 on a plain dict for a new key raises KeyError. Fix: counts.get(w, 0) + 1, defaultdict(int) or Counter.
- Using a list or a set as the group key. Fix: a sorted string or a tuple of counts.
- No tie rule in top-k, so the output can change between programs or languages. Fix: sort by (−count, item) and say the rule aloud.
- In subarray-sum, forgetting seen = {0: 1}, or storing running before looking it up. Fix: start with the empty prefix; look up first, then store.
- Returning groups in whatever order the dict gives when the task asks for a fixed order. Fix: sort exactly as the task says.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Counting and grouping with hashing · 1/8
This lesson uses hash maps to count, to group, and to count subarrays with a given sum.
Prefer a video by a teacher? Find free videos on “group anagrams top k frequent subarray sum equals k prefix sum hashmap” on YouTube ↗
In real life: Most ordered dishes in each city
A food delivery app shows "popular near you". An analytics job can count orders by the pair (city, dish): a tuple is a valid dict key, so one Counter over a day's orders gives every count in one pass, and a sort with a tie rule picks the top dishes per city. Real systems run this over huge logs with SQL GROUP BY or big-data tools, but the idea is the same frequency map.
from collections import Counter
orders = [("Pune", "misal pav"), ("Pune", "vada pav"), ("Chennai", "dosa"),
("Pune", "misal pav"), ("Chennai", "dosa"), ("Chennai", "idli")]
counts = Counter(orders)
pune = sorted((dish for city, dish in counts if city == "Pune"),
key=lambda d: (-counts[("Pune", d)], d))
print(pune)Practice (0/4 done)
Write first_unique_char(s) that returns the index of the first character that appears exactly once in s, or -1 if there is none. Case matters ("a" and "A" are different) and spaces count as characters. O(n) time. Example: first_unique_char("loveleetcode") returns 2.
Write group_anagrams(words) that groups words that are anagrams of each other (same letters, same counts). For a fixed answer: sort the words inside each group alphabetically, then sort the list of groups (Python compares lists item by item, so groups are ordered by their first word). Example: group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]) returns [["ate", "eat", "tea"], ["bat"], ["nat", "tan"]].
Write subarray_sum_count(nums, k) that returns how many subarrays (runs of one or more neighbouring items) have a sum exactly equal to k. Numbers can be negative or zero. Use prefix sums with a dict: O(n) time. Example: subarray_sum_count([1, 2, 3], 3) returns 2 ([1, 2] and [3]).
Write charFrequency(s) in JavaScript. Count every character of s with a Map, and return an array of [char, count] pairs sorted by count from high to low; for equal counts, put the smaller character first (compare with < and >, which uses character codes, so "A" comes before "a" and " " before letters). Example: charFrequency("banana") returns [["a", 3], ["n", 2], ["b", 1]]. An empty string gives [].
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Two pointersHashing, two pointers and sliding window · same module
- Sliding window: fixed sizeHashing, two pointers and sliding window · same module
- Data-structure questions in JavaScriptJavaScript Programming (language in depth) · Interview preparation and career
- Recognising the patternInterview strategy and mock problems
- Tries (prefix trees)Tries, union-find and bit manipulation
- Strings: building, comparing and countingArrays and strings
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does Counter("banana")["z"] return?
Answer: 0
Counter treats a missing key as a count of 0 instead of raising KeyError.
Which key puts exactly the anagrams of "listen" into one group AND can be used as a dict key?
Answer: "".join(sorted("listen"))
The sorted string is the same for all anagrams and is hashable. A set loses counts and is unhashable, a list is unhashable, and length groups unrelated words.
For nums = [2, -2, 2] and k = 0, how many subarrays sum to k?
Answer: 2
[2, −2] and [−2, 2] both sum to 0; [2, −2, 2] sums to 2.
In the subarray-sum pattern, why look up running − k BEFORE storing running?
Answer: So a prefix is never matched with itself, which would count an empty subarray when k = 0
If running were stored first, running − 0 would find the prefix just stored.
Top-k words with the rule "higher count first, then alphabetical". Which sort key?
Answer: key=lambda w: (-counts[w], w)
The minus sign sorts counts from high to low; the word breaks ties alphabetically.
How much extra space does "first unique character" need for a string of lowercase English letters?
Answer: O(1), at most 26 counts
The count map has at most one entry per letter, and there are only 26 letters.
Why does the subarray-sum-equals-k pattern start with seen = {0: 1}?Related
Answer: So subarrays that start at index 0 are counted
A subarray from index 0 to r sums to k when the running total equals k, i.e. running − k = 0, the empty prefix.
Which pattern fits "find two numbers that add up to a target" in unsorted data?Related
Answer: Hash map
Store what you have seen and look up the complement.
Why must the numbers be positive for the shortest-subarray-with-sum-at-least-target window?Related
Answer: So removing an item from the left always lowers the sum, which makes "shrink while valid" correct
With negatives, removing an item can raise the sum, so the window can stop shrinking too early or miss the answer.
The window [4, 2, 7] has sum 13. The next item is 1 and k = 3. What is the next window sum?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Hashing, two pointers and sliding window” before its exam: Two pointers, Sliding window: fixed size, Sliding window: variable size.
- Module 3 exam: Hashing, two pointers and sliding window · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Stacks, queues and linked lists. 61 topics left in the course before the final exam.