Chapter 12: Maps and Hash Tables
Multiple Choice · Maps and the Idea of Hashing
Question 1: The Map ADT and Hash Tables
Which statement about the relationship between a map and a hash table is true?
C is correct: a map is an ADT, which a sorted array, a search tree, or a hash table can implement. A has it backwards. B is wrong: keys are unique, and put with an existing key only updates the value. D is wrong: O(1) (on average) is specific to hash tables; in an unsorted list, get is O(n).
Short Answer · Maps and the Idea of Hashing
Question 2: What Map Operations Return
What does this code print? Pay attention to what put and remove return.
Map<Integer, String> m = new HashMap<>();
System.out.println(m.put(105, "Murat"));
m.put(21, "Leyla");
System.out.println(m.put(21, "Ali"));
System.out.println(m.get(21) + " " + m.size());
System.out.println(m.remove(105));
System.out.println(m.remove(105));
System.out.println(m.get(88) + " "
+ m.containsKey(21));
Show answer
Output (one line per println):
null Leyla Ali 2 Murat null null true
putreturnsnullfor a new key and the old value for an existing one; the secondputfor 21 is an update, so the size stays at 2.removereturns the removed value, ornullonce the key is gone.get(88): there is no such key →null.
Multiple Choice · Hash Functions
Question 3: A Good Hash Function
Which of the following is not something we can expect of a good hash function?
D is too much to ask: when the key space is larger than the table, the pigeonhole principle makes collisions inevitable. The goal is to reduce collisions; resolving them is the job of chaining or open addressing. A is the strongest distractor, but it is required: with a nondeterministic function, an inserted entry can never be found again.
Multiple Choice · Hash Functions
Question 4: MAD Compression
The MAD (multiply-add-divide) method h(k) = ((a·k + b) mod p) mod N is used with a = 3, b = 4, p = 17, N = 5. What is h(7)?
B: 3·7 + 4 = 25 → 25 mod 17 = 8 → 8 mod 5 = 3. A forgets the final mod N (8 is not a valid index in the table). C skips mod p and takes 25 mod 5. D is just 7 mod 5 (the division method).
Multiple Choice · Hash Functions
Question 5: The equals/hashCode Contract
Which statement about Java's equals/hashCode contract is true?
C is correct: equal keys must land in the same bucket. A is the converse, and it is false: equal hash codes only mean the same bucket, where keys are still compared with equals (a collision). B is impossible to guarantee. D is the classic mistake: hashCode picks the bucket, and without an override equal keys land in different buckets.
Short Answer · Hash Functions
Question 6: The Forgotten hashCode
The Point class overrides equals but not hashCode. What does the last line print in practice, and why isn't that guaranteed? Fix the bug.
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } public boolean equals(Object o) { return o instanceof Point p && x == p.x && y == p.y; } } Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 2), "A"); Point q = new Point(1, 2); System.out.println(q.equals(new Point(1, 2)) + " " + m.get(q));
Show answer
Output: true null
The two points are equal according to equals, but since hashCode isn't overridden, Object.hashCode() (object identity) is used and the codes differ. HashMap finds the bucket with this code and, within the bucket, compares hash codes before calling equals: if the codes differ, equals is never called → null. It isn't guaranteed because two identity hashes can coincide by chance.
The fix: override hashCode too, using the same fields (or make Point a record):
public int hashCode() { return Objects.hash(x, y); }
Code Trace · Hash Functions
Question 7: Tracing a String Hash Code
What does this code print? (It computes the same thing as String.hashCode().)
hash("Ece") is called. Character codes: E = 69, c = 99, e = 101.
Start with h = 0.
0 < 3 → the loop continues; s.charAt(0) = 'E' (69).
h = 31 · 0 + 69 = 69.
1 < 3 → s.charAt(1) = 'c' (99).
h = 31 · 69 + 99 = 2139 + 99 = 2238. The running total is multiplied by 31, so position matters.
2 < 3 → s.charAt(2) = 'e' (101).
h = 31 · 2238 + 101 = 69378 + 101 = 69479.
3 < 3 is false → the loop ends; return 69479 (= 69·31² + 99·31 + 101).
Compression: 69479 = 13 · 5344 + 7 → floorMod(69479, 13) = 7.
Output: 69479 7. In a 13-bucket table, "Ece" lands in bucket 7.
int hash(String s) { int h = 0; for (int i = 0; i < s.length(); i++) h = 31 * h + s.charAt(i); return h; } // main: int code = hash("Ece"); int index = Math.floorMod(code, 13); System.out.println(code + " " + index);
Short Answer · Separate Chaining
Question 8: A Separate Chaining Table
The keys 22, 1, 13, 11, 24, 33, 18, 42, 31 are inserted in order into a separate-chaining table with N = 7 buckets and h(k) = k mod 7; each new node goes at the head of its chain. Draw the final table and find the load factor.
Show answer
| Bucket | Chain (head to tail) |
|---|---|
| 0 | 42 |
| 1 | 1 → 22 |
| 2 | – |
| 3 | 31 → 24 |
| 4 | 18 → 11 |
| 5 | 33 |
| 6 | 13 |
Buckets: 22→1, 1→1, 13→6, 11→4, 24→3, 33→5, 18→4, 42→0, 31→3. A colliding key goes to the head of the chain (1, which arrives after 22, is first).
λ = n / N = 9 / 7 ≈ 1.29. With chaining, λ > 1 is fine: λ is the average chain length, and an unsuccessful search examines λ nodes on average.
Multiple Choice · Separate Chaining
Question 9: %, floorMod, and Math.abs
What does this line print?
System.out.println((-7 % 10) + " " + Math.floorMod(-7, 10) + " " + Math.abs(Integer.MIN_VALUE));
A: in Java, the result of % takes the sign of the dividend (−7); floorMod adds N to a negative result (3). The positive counterpart of Integer.MIN_VALUE doesn't fit in an int, so Math.abs leaves it negative. That is why a bucket index uses Math.floorMod(h, N) rather than % N or Math.abs(h) % N; the other two can cause an ArrayIndexOutOfBoundsException.
Short Answer · Separate Chaining
Question 10: Collisions in a Hash Table
A hash table with 5 buckets uses h(k) = k mod 5 and resolves collisions by chaining (appending to the end of the bucket). After inserting 12, 7, 20, 3, 17 in order, what do the buckets look like, and how many key comparisons does a search for 17 take?
Show answer
| Bucket | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Chain | 20 | — | 12 → 7 → 17 | 3 | — |
12, 7 and 17 all land in bucket 2 (collisions). For 17, h(17) = 2 takes us straight to bucket 2, where the chain is checked: 12, 7, 17 → 3 comparisons.
If keys spread evenly over the buckets, insert, search and delete are O(1) on average; if they all land in one bucket, the chain has length n → O(n) in the worst case. Java's HashMap and HashSet are built on this idea.
Multiple Choice · Open Addressing
Question 11: Open Addressing or Chaining?
Comparing open addressing with separate chaining, which statement is true?
D is correct: all pairs live in the array itself (cache-friendly), but the number of probes explodes as λ → 1. A is true of chaining; under open addressing there is one pair per slot, so n ≤ N and λ ≤ 1. B breaks probe sequences; a "deleted" marker is needed. C is wrong too: with chaining, buckets are lists, and λ > 1 is allowed.
Code Trace · Open Addressing
Question 12: Tracing Linear Probing
What does table look like when the loop finishes, and how many probes does each key take?
10 mod 7 = 3 (home slot).
i = 0: slot 3 is empty → table[3] = 10 (1 probe).
17 mod 7 = 3: slot 3 holds 10, a collision.
i = 1: slot 4 is empty → table[4] = 17 (2 probes).
24 mod 7 = 3: slot 3 again.
3 (10) and 4 (17) are occupied; i = 2: slot 5 is empty → table[5] = 24 (3 probes).
3 mod 7 = 3: slot 3 for the fourth time.
3, 4 and 5 are occupied; i = 3: slot 6 is empty → table[6] = 3 (4 probes).
5 mod 7 = 5: a different home slot, but inside the cluster 3–6.
5 (24) and 6 (3) are occupied; i = 2: (5 + 2) mod 7 = 0 is empty → wrap around: table[0] = 5 (3 probes).
Result: [5, –, –, 10, 17, 24, 3]; 13 probes in total. The adjacent occupied slots formed a single cluster: primary clustering.
Integer[] table = new Integer[7]; for (int k : new int[]{10, 17, 24, 3, 5}) { int h = k % 7; // home slot for (int i = 0; i < 7; i++) { int j = (h + i) % 7; if (table[j] == null) { table[j] = k; break; } // occupied: i++ } }
Short Answer · Open Addressing
Question 13: Quadratic Probing
Insert the same keys (10, 17, 24, 3, 5) with N = 7 and h(k) = k mod 7, this time using quadratic probing ((h + i²) mod 7). Give the final table and the slots each key tries.
Show answer
| k | h | Slots probed |
|---|---|---|
| 10 | 3 | 3 |
| 17 | 3 | 3, 4 |
| 24 | 3 | 3, 4, (3+4) mod 7 = 0 |
| 3 | 3 | 3, 4, 0, (3+9) mod 7 = 5 |
| 5 | 5 | 5, 6 |
Table: 0: 24, 3: 10, 4: 17, 5: 3, 6: 5 (1 and 2 empty).
Because the jumps grow, probes escape a cluster quickly (less primary clustering). But 10, 17, 24 and 3 share a home slot, so they all follow the same sequence (3, 4, 0, 5…): secondary clustering.
Multiple Choice · Open Addressing
Question 14: The Second Hash Function
In double hashing (N = 13), why is the second function h₂(k) = 7 − (k mod 7) rather than h₂(k) = k mod 7?
B: if k is a multiple of 7, k mod 7 = 0, the probe sequence is h, h, h, …, and no empty slot is ever found. The step must also be coprime to N; since N = 13 is prime, every step from 1 to 7 visits all slots. C is wrong: different keys can get the same step (e.g., 31 and 59 → 4); collisions drop but don't vanish.
Code Trace · Open Addressing
Question 15: findSlot() and the Deleted Marker
The table is [5, –, –, 10, ✗, 24, 3] (N = 7, ✗ = DEFUNCT). What does findSlot(31) return, and which slot does put(31) put 31 in?
N = 7; 17 was deleted, leaving ✗ in slot 4. h = 31 mod 7 = 3.
i = 0: slot 3 is occupied, 10 ≠ 31 → next slot.
i = 1: slot 4 is DEFUNCT → the first available slot: avail = 4.
Deleted ≠ empty: no break; keep searching (31 may lie beyond).
i = 2: slot 5 is occupied, 24 ≠ 31.
i = 3: slot 6 is occupied, 3 ≠ 31.
i = 4: (3 + 4) mod 7 = 0, occupied, 5 ≠ 31.
i = 5: slot 1 is truly empty. avail is already 4 and stays; break: 31 can't lie beyond this.
31 is absent: return −(4 + 1) = −5.
put(31): j = −5 < 0 → table[−(j + 1)] = table[4] = 31. The deleted slot is reused.
private int findSlot(int k) { int h = Math.floorMod(k, N), avail = -1; for (int i = 0; i < N; i++) { int j = (h + i) % N; // linear if (table[j] == null || table[j] == DEFUNCT) { if (avail == -1) avail = j; if (table[j] == null) break; } else if (table[j].key == k) return j; // found } return -(avail + 1); // absent: encoded slot }
Short Answer · Performance and Applications
Question 16: Load Factor and Probe Counts
Under linear probing, the expected number of probes for an unsuccessful search (Knuth) is ½(1 + 1/(1−λ)²). Compute it for λ = 0.5 and λ = 0.8. How does the result explain the advice to keep λ ≤ 0.5 under open addressing?
Show answer
- λ = 0.5: ½(1 + 1/0.25) = ½(1 + 4) = 2.5 probes.
- λ = 0.8: ½(1 + 1/0.04) = ½(1 + 25) = 13 probes.
Filling the table from just 50% to 80% makes an unsuccessful search about 5 times as expensive, and as λ → 1 the cost goes to infinity (clusters merge). So open addressing keeps λ ≤ 0.5 and roughly doubles the table once the threshold is crossed.
(Compare chaining: the expected cost is O(1 + λ), and a threshold of λ ≈ 0.75 is enough.)
Multiple Choice · Performance and Applications
Question 17: Inside Java's HashMap
Which statement about Java's HashMap is false?
D is false (a common mistake): iteration follows bucket order and can change when the table grows. For insertion order use LinkedHashMap; for key order use TreeMap. A, B and C are the HashMap details from the slides (treeification happens when a chain exceeds 8 and the table has ≥ 64 buckets).
Multiple Choice · Performance and Applications
Question 18: A Poor Fit for a Hash Table
For which of these tasks is a hash table not a good choice?
B: a hash table scatters keys and keeps no order, so range queries and in-order traversal cost O(n) (plus sorting). Search trees suit this task. A, C and D ask "is it there, and what is its value?" for an exact key: O(1) on average.
Short Answer · Performance and Applications
Question 19: twoSum()
What does twoSum(new int[]{3, 8, 4, 3, 6}, 6) return? Track the contents of the map seen at each step. What is the algorithm's time complexity?
static int[] twoSum(int[] a, int target) { Map<Integer, Integer> seen = new HashMap<>(); for (int i = 0; i < a.length; i++) { int need = target - a[i]; if (seen.containsKey(need)) return new int[]{seen.get(need), i}; seen.put(a[i], i); // value -> index } return null; }
Show answer
| i | a[i] | need | containsKey | seen (after) |
|---|---|---|---|---|
| 0 | 3 | 3 | false | {3=0} |
| 1 | 8 | −2 | false | {3=0, 8=1} |
| 2 | 4 | 2 | false | {3=0, 8=1, 4=2} |
| 3 | 3 | 3 | true | – |
Returns [0, 3] (3 + 3 = 6). Because the loop checks the map before inserting, an element never pairs with itself, yet two copies of the same value are still found.
One pass at O(1) per step on average → O(n) time and O(n) extra memory (brute force is O(n²)).
Short Answer · Summary
Question 20: A Mutable Key
A list is modified after being used as a key in a HashMap. What does the code print, and why?
List<Integer> k = new ArrayList<>(); k.add(1); k.add(2); Map<List<Integer>, String> m = new HashMap<>(); m.put(k, "A"); k.add(3); System.out.println(m.get(k)); System.out.println(m.size());
Show answer
Output: null and 1 (two lines).
During put, the bucket was chosen by the hash code of the list [1, 2]. After k.add(3), the list's hashCode() changed, so get(k) looks in a different bucket and finds nothing. The pair is still in the table (size() = 1), but it can no longer be reached.
The rule: fields used by equals/hashCode must not change after insertion; prefer immutable types such as String, Integer, or a record as keys.
Short Answer · Performance and Applications
Question 21: Resizing Past the Threshold
A chained table with N = 7 buckets uses h(k) = k mod N, and new nodes go at the head of the chain. After each insertion, if size / N > 0.75, the slides' resize() is called: newN = 2N + 1, the old buckets are visited from 0 upward, and each chain is moved head to tail into the new array with put.
The keys 8, 15, 22, 3, 10, 30 are inserted in order. After which insertion does the table grow? Give the final table and load factor.
Show answer
λ goes 1/7, …, 5/7 ≈ 0.71 (below the threshold). Inserting 30 gives 6/7 ≈ 0.86 > 0.75 → the table grows after 30; newN = 2·7 + 1 = 15.
Before growing: bucket 1: 22 → 15 → 8, bucket 2: 30, bucket 3: 10 → 3. Transfer order: 22, 15, 8, 30, 10, 3 (k mod 15).
| Bucket | 0 | 3 | 7 | 8 | 10 |
|---|---|---|---|---|---|
| Chain | 30 → 15 | 3 | 22 | 8 | 10 |
The other buckets are empty; λ = 6/15 = 0.4. 8, 15 and 22, once in the same chain, are now in separate buckets, while 15 and 30 (both 0 mod 15) now collide. The old indices are meaningless, so every key is rehashed: this insertion costs O(n), but the amortized cost per insertion is O(1).
Short Answer · Open Addressing
Question 22: Inserting with Double Hashing
With N = 11, h(k) = k mod 11, h₂(k) = 7 − (k mod 7) and j = (h + i · h₂) mod 11 (i = 0, 1, 2, …), the keys 49, 54, 33, 93, 59, 60 are inserted in order into an empty table. Give the slots each key tries and the final table.
Show answer
| k | h | h₂ | Slots probed |
|---|---|---|---|
| 49 | 5 | 7 | 5 |
| 54 | 10 | 2 | 10 |
| 33 | 0 | 2 | 0 |
| 93 | 5 | 5 | 5, 10, (5 + 10) mod 11 = 4 |
| 59 | 4 | 4 | 4, 8 |
| 60 | 5 | 3 | 5, 8, (5 + 6) mod 11 = 0, 3 |
Table: 0: 33, 3: 60, 4: 93, 5: 49, 8: 59, 10: 54 (the rest empty).
93 and 60 share a home slot (5) but have different steps (5 and 3): one jumps to 10, the other to 8, so they don't follow the same sequence as they would under linear or quadratic probing. Every step from 1 to 7 is coprime to N = 11, so a probe sequence can reach every slot.
Multiple Choice · Performance and Applications
Question 23: The HashMap Bucket Index
Given that 131079 = 0x20007 = 2 · 65536 + 7, what does the last line print?
static int index(Object key, int n) { int h = key.hashCode(); int m = h ^ (h >>> 16); return m & (n - 1); // n = 2^k } System.out.println(index(131079, 16));
Correct: A. An Integer's hash code is its value: h = 0x20007, and h >>> 16 = 2. Low 4 bits: 0111 ^ 0010 = 0101 = 5. B skips the mixing: 131079 & 15 = 7, and the high bits never reach the index. C adds instead of XORing: 7 + 2 = 9. D masks with & n (10000₂), which sees just one bit. Since n is a power of 2, & (n − 1) takes the low k bits and keeps the result in 0…n − 1.
Multiple Choice · Open Addressing
Question 24: Piled-Up Deleted Markers
After many insertions and deletions, a linear-probing table (N = 7) looks like [✗, 8, ✗, ✗, 11, ✗, ✗] (✗ = DEFUNCT): only 2 live keys, λ = 2/7. How many slots does the slides' findSlot(19) examine, and what does it return?
Correct: C. h = 19 mod 7 = 5, and the loop stops only at null. Slots 5, 6, 0, 2, 3 are ✗ and 8 and 11 don't match: 7 probes. The first ✗ slot is 5, so avail = 5 and the result is −(5 + 1) = −6. A treats ✗ as empty: the search ends early and can miss a key further on. B is wrong: ✗ slots count as available. D forgets the if (avail == -1) test. Even with a small λ, ✗ markers lengthen searches; so we track (live + ✗)/N, and past the threshold the table is rehashed and the ✗ markers are dropped.