Chapter 12: Maps and Hash Tables

Chapter 12: Maps and Hash Tables · Study Questions
Maps and hashingHash functionsequals and hashCodeSeparate chainingOpen addressingLoad factor and resizingJava's HashMap and applications
Sercan KÜLCÜ · Giresun University · Computer Engineering

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
  • put returns null for a new key and the old value for an existing one; the second put for 21 is an update, so the size stays at 2.
  • remove returns the removed value, or null once 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
BucketChain (head to tail)
042
11 → 22
2–
331 → 24
418 → 11
533
613

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
Bucket01234
Chain20—12 → 7 → 173—

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?

0123456
0123456

10 mod 7 = 3 (home slot).

012103j456

i = 0: slot 3 is empty → table[3] = 10 (1 probe).

012103456

17 mod 7 = 3: slot 3 holds 10, a collision.

012103174j56

i = 1: slot 4 is empty → table[4] = 17 (2 probes).

01210317456

24 mod 7 = 3: slot 3 again.

012103174245j6

3 (10) and 4 (17) are occupied; i = 2: slot 5 is empty → table[5] = 24 (3 probes).

0121031742456

3 mod 7 = 3: slot 3 for the fourth time.

01210317424536j

3, 4 and 5 are occupied; i = 3: slot 6 is empty → table[6] = 3 (4 probes).

01210317424536

5 mod 7 = 5: a different home slot, but inside the cluster 3–6.

50j1210317424536

5 (24) and 6 (3) are occupied; i = 2: (5 + 2) mod 7 = 0 is empty → wrap around: table[0] = 5 (3 probes).

501210317424536

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
khSlots probed
1033
1733, 4
2433, 4, (3+4) mod 7 = 0
333, 4, 0, (3+9) mod 7 = 5
555, 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?

5012103✗424536
5012103✗424536

N = 7; 17 was deleted, leaving ✗ in slot 4. h = 31 mod 7 = 3.

5012103j✗424536

i = 0: slot 3 is occupied, 10 ≠ 31 → next slot.

5012103✗4j24536

i = 1: slot 4 is DEFUNCT → the first available slot: avail = 4.

5012103✗4j24536

Deleted ≠ empty: no break; keep searching (31 may lie beyond).

5012103✗4245j36

i = 2: slot 5 is occupied, 24 ≠ 31.

5012103✗424536j

i = 3: slot 6 is occupied, 3 ≠ 31.

50j12103✗424536

i = 4: (3 + 4) mod 7 = 0, occupied, 5 ≠ 31.

501j2103✗424536

i = 5: slot 1 is truly empty. avail is already 4 and stays; break: 31 can't lie beyond this.

5012103✗424536

31 is absent: return −(4 + 1) = −5.

501210331424536

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
ia[i]needcontainsKeyseen (after)
033false{3=0}
18−2false{3=0, 8=1}
242false{3=0, 8=1, 4=2}
333true–

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).

Bucket037810
Chain30 → 15322810

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
khh₂Slots probed
49575
5410210
33020
93555, 10, (5 + 10) mod 11 = 4
59444, 8
60535, 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.

The End

Chapter 12: Maps and Hash Tables: Study Questions
Lecture slides: Chapter 12: Maps and Hash Tables
1 / 1 Sercan KÜLCÜ. All rights reserved.