3.3.10 · D4Hashing

Exercises — Applications — frequency counting, two-sum problem, caching (LRU)

2,516 words11 min readBack to topic

This page is a self-test. Each problem sits above a collapsible solution — try it first, then open. Difficulty climbs in five levels:

Prerequisites live in the parent parent topic and in Hash Functions, Collision Resolution (Chaining vs Open Addressing), Doubly Linked List, Sliding Window, Time Complexity Analysis, and Caching Strategies.


L1 — Recognition

Exercise 1.1

You are asked: "How many times does each word appear in a document?" Which single data structure turns this into one linear pass, and what is stored as key and as value?

Recall Solution

Tool: a hash map used as a frequency map. Key = the word (a string), value = the running count (an integer). Why this and not sorting? Sorting all words to group duplicates costs ; a map counts in because each lookup/increment is average (see Time Complexity Analysis). One pass: for each word w, do count[w] += 1 (missing → default 0).

Exercise 1.2

In Two-Sum with target = 10, you are currently looking at the number x = 4. Name the exact value you should look up in your map, and say what the map maps from and to.

Recall Solution

Look up the complement . The map stores value → index of every element strictly to the left of the current one. The question "is a valid partner already available?" is literally "is a key in the map?".


L2 — Application

Exercise 2.1

Build the frequency map for the string "mississippi" by hand. Then name the first non-repeating character.

Recall Solution

Walk left to right, incrementing each character:

char map after
m {m:1}
i {m:1,i:1}
s {m:1,i:1,s:1}
s {m:1,i:1,s:2}
i {m:1,i:2,s:2}
s {m:1,i:2,s:3}
s {m:1,i:2,s:4}
i {m:1,i:3,s:4}
p {m:1,i:3,s:4,p:1}
p {m:1,i:3,s:4,p:2}
i {m:1,i:4,s:4,p:2}

Final counts: m:1, i:4, s:4, p:2. Check: length, so nothing was lost. First non-repeating: re-scan the original string in order and return the first char whose count is . That is m (position 0). Why two passes? Pass 1 builds totals; pass 2 uses the original left-to-right order to break ties. Still total.

Exercise 2.2

Run the one-pass Two-Sum on nums = [3, 2, 4], target = 6. Show the table and give the answer.

Recall Solution

Recall the invariant: when we reach index , seen holds exactly nums[0..j-1].

j x c = 6−x c in seen? seen after
0 3 3 no (seen empty) {3:0}
1 2 4 no {3:0, 2:1}
2 4 2 yes → index 1

At the complement was stored at index , so the answer is (1, 2). Note we did not match the first 3 with itself: at we checked before inserting, and seen was empty.

Exercise 2.3

For a cache of capacity 2, run this sequence and give the final list order (front→back) and the final map keys: put(1,A), put(2,B), get(1), put(3,C), put(4,D), get(2).

Recall Solution
op list (front→back) map keys note
put(1,A) 1 {1} new → front
put(2,B) 2,1 {1,2} 2 newest
get(1)→A 1,2 {1,2} touching 1 moves it to front
put(3,C) 3,1 {1,3} full → evict back = 2
put(4,D) 4,3 {3,4} full → evict back = 1
get(2)→-1 4,3 {3,4} 2 evicted long ago

Final: list order 4,3; map keys {3,4}. get(2) returns -1 (miss). Why 1 was the victim at put(4)? After put(3), the back was 1 (it hadn't been touched since get(1)), so it became the least-recently-used and got evicted.

Figure — Applications — frequency counting, two-sum problem, caching (LRU)

L3 — Analysis

Exercise 3.1

A student claims Two-Sum by "sort then two-pointer" is strictly better because it uses extra space. State the precise flaw and the exact cost of the correct hash approach.

Recall Solution

Flaw: the problem asks for the original indices, but sorting destroys them. To recover indices you must sort (value, index) pairs, which reintroduces auxiliary space — so the promised space is not real for this problem. Also the sort costs . Correct hash cost: time , space for the map. So hashing is asymptotically faster ( vs ) and keeps indices for free.

Exercise 3.2

Explain, with the pointer-surgery equations, why LRU needs a doubly linked list and not a singly linked one. Then give the two-line unlink operation.

Recall Solution

To make a touched node "most recent" in , we must first splice it out of its current spot. Splicing requires rewiring the node before it — its predecessor.

  • A Doubly Linked List stores prev, so the predecessor is known instantly: Two pointer writes → .
  • A singly linked list has no prev; to find the predecessor you would scan from the head, which is . That would break the contract for get/put.

Exercise 3.3

For frequency counting over a list of items with distinct values, give the tight time and space in big-O, and state the exact worst case for space.

Recall Solution

Time: every element triggers one hash + one map update, each average, so . Space: the map holds one entry per distinct key: . Worst case space: when all values are distinct, , giving . (See Collision Resolution (Chaining vs Open Addressing) for why "average " can degrade to per op under adversarial collisions — the map may need resizing/good hashing to hold.)


L4 — Synthesis

Exercise 4.1

Longest substring without repeating characters. Combine a frequency/last-seen map with the Sliding Window technique to solve "abcabcbb" in . Give the length and the substring.

Recall Solution

Idea: keep a window [L..R] with all-distinct chars. Store last[ch] = last index seen. When the char at R was seen inside the window, jump L to last[ch] + 1 (never move L backward). Track the max window width.

R ch last[ch] before new L window width
0 a 0 a 1
1 b 0 ab 2
2 c 0 abc 3
3 a 0 (≥L) 1 bca 3
4 b 1 (≥L) 2 cab 3
5 c 2 (≥L) 3 abc 3
6 b 4 (≥L) 5 cb 2
7 b 6 (≥L) 7 b 1

Max width = 3, achieved by substring "abc". Each of L, R moves forward at most times → . The map gives "have I seen this char, and where?".

Exercise 4.2

Two-Sum with a twist: count all pairs. Given nums = [1, 5, 3, 3, 5], target = 6, count the number of unordered index pairs (i, j) with nums[i] + nums[j] = target. Use a frequency map.

Recall Solution

Values summing to 6: pairs of complements are (1,5) and (3,3). Build frequency map: {1:1, 5:2, 3:2}.

  • Distinct complements a + b = 6, a < b: only 1 + 5. Count = pairs.
  • Equal halves a = b = 3: choose 2 of the two 3's = pair. Total = unordered pairs. Why the for equal halves? A value can't pair with itself using the same index; we count distinct index pairs among the copies.

L5 — Mastery

Exercise 5.1

Design under constraint. You must support get/put in and also answer getMostRecent() in (return the most-recently-used key). Which sentinel pointer answers it, and why is it ?

Recall Solution

Use the same map + Doubly Linked List design. Keep a head sentinel on the most-recent side. Then getMostRecent() = head.next.k — the node immediately after the head is always the newest. It is because it's a single pointer dereference; every get/put already maintains the invariant "front = most recent" by splicing touched nodes right behind head. No scan needed. (Symmetrically, the least recent is tail.prev.k, which is exactly the eviction victim.)

Exercise 5.2

Edge cases of LRU. For capacity 1, trace put(1,A), put(2,B), get(1), put(2,C), get(2). Give every return value and the final state.

Recall Solution
op map list (front→back) return
put(1,A) {1:A} 1
put(2,B) {2:B} 2 — (evicted 1: over cap)
get(1) {2:B} 2 -1 (1 was evicted)
put(2,C) {2:C} 2 — (key exists → update value, move to front)
get(2) {2:C} 2 C

Final state: map {2:C}, list 2. Why put(2,C) does not evict? Key 2 already exists, so we _remove its old node and re-add — the map size never exceeds capacity, so no eviction fires. This is the degenerate capacity-1 case the design must still handle correctly.

Exercise 5.3

Degenerate frequency input. What does the first-non-repeating-character algorithm return for the empty string "", and for "aabb"? Justify from the algorithm's definition.

Recall Solution
  • "": the frequency map is empty; the second pass has nothing to scan, so there is no non-repeating character → return a sentinel such as -1 / None. (Handle the empty case explicitly.)
  • "aabb": frequency map {a:2, b:2}. Every count is , so the second pass finds no char with count 1 → return -1 / None. The result "no unique char exists" is a valid, non-crashing outcome the caller must expect.
Recall Feynman one-liner

Frequency counting ::: a tally sheet: one line per distinct thing, add a stroke each time you see it. Two-Sum complement ::: "I need a friend who adds up to the target — have I already met them?" LRU pairing ::: the map is a phone book (find fast); the linked list is a queue of who you called last (evict fast).