Chapter 13: Sets and Disjoint Sets
Short Answer · Set ADT
Question 1: Set Algebra with Set Methods
What does the program print? Name the set operation each method performs.
Set<Integer> a =
new TreeSet<>(List.of(5, 1, 9, 3, 7));
Set<Integer> b =
new HashSet<>(List.of(3, 4, 5, 6));
Set<Integer> i = new TreeSet<>(a);
i.retainAll(b);
Set<Integer> d = new TreeSet<>(a);
d.removeAll(b);
Set<Integer> u = new TreeSet<>(a);
u.addAll(b);
System.out.println(i + " " + d + " "
+ u.size() + " " + a.add(3));
Show answer
retainAll= intersection A ∩ B = [3, 5]removeAll= difference A − B = [1, 7, 9]addAll= union = [1, 3, 4, 5, 6, 7, 9] → size 7- 3 is already in a:
addreturnsfalseand the set is unchanged.
Output: [3, 5] [1, 7, 9] 7 false
All three methods modify the set in place, which is why a is copied first (new TreeSet<>(a)). Since these are TreeSets, the results print in sorted order.
Multiple Choice · Set ADT
Question 2: Copy or Reference?
What does the program print?
Set<Integer> a =
new TreeSet<>(List.of(1, 2, 3, 4));
Set<Integer> b =
new TreeSet<>(List.of(3, 4, 5));
Set<Integer> both = a;
both.retainAll(b);
System.out.println(a + " " + both.size());Correct: C. both = a makes no copy; both variables refer to the same object. Because retainAll works in place, a also becomes [3, 4]. Option A assumes a was copied; to take the intersection without changing a, you need new TreeSet<>(a).
Multiple Choice · Set Implementations
Question 3: Iteration Order
What does the program print?
Set<String> l = new LinkedHashSet<>(); Set<String> t = new TreeSet<>(); for (String s : List.of("plum", "apple", "cherry", "apple")) { l.add(s); t.add(s); } System.out.println(l + " " + t);
Correct: A. LinkedHashSet keeps insertion order (a hash table plus a doubly linked list); TreeSet is a red-black tree and iterates alphabetically. B is wrong: the second "apple" is added to neither set, because a set holds no duplicates.
Short Answer · Set Implementations
Question 4: A Chained Hash Set
The values 9, 16, 5, 30, 12, 16 are added in order to a chained hash set with m = 7 buckets (bucket = x % 7). Give the final state of the buckets, then show which elements a subsequent contains(23) examines.
Show answer
| add | 9 | 16 | 5 | 30 | 12 | 16 |
|---|---|---|---|---|---|---|
| x % 7 | 2 | 2 | 5 | 2 | 5 | 2 |
| returns | true | true | true | true | true | false |
Final state: bucket 2: 9 → 16 → 30, bucket 5: 5 → 12; all other buckets empty; n = 5. For the second 16, bucket 2 is scanned, 16 is found, and nothing is added: this is what makes a set a set.
contains(23): 23 % 7 = 2 → it compares only against 9, 16, 30 in bucket 2 (3 comparisons), finds nothing → false. No other bucket is examined; as long as the average chain length n/m stays constant, the operation is O(1).
Short Answer · Set Implementations
Question 5: Ordered Queries with TreeSet
What does the program print? How would the cost change if these queries ran on a HashSet?
TreeSet<Integer> s = new TreeSet<>(
List.of(8, 3, 14, 21, 5, 17, 30));
System.out.println(s.first() + " "
+ s.last() + " " + s.floor(10) + " "
+ s.ceiling(17) + " " + s.subSet(5, 21));
Show answer
Output: 3 30 8 17 [5, 8, 14, 17]
first/last: smallest 3, largest 30.floor(10): the largest element ≤ 10 → 8.ceiling(17): the smallest element ≥ 17 → 17 itself (equality counts).subSet(5, 21): lower bound included, upper bound excluded → [5, 8, 14, 17].
In a balanced tree each is O(log n) (a range query adds k for the output). A HashSet keeps no order, so the same queries must scan every element: O(n).
Multiple Choice · Set Implementations
Question 6: TreeSet and compare
The Person class has fields firstName and lastName. What does the program print?
TreeSet<Person> s = new TreeSet<>( Comparator.comparing((Person p) -> p.lastName)); s.add(new Person("Ann", "Smith")); s.add(new Person("Alice", "Smith")); s.add(new Person("Ben", "Jones")); System.out.println(s.size());
Correct: D. TreeSet decides equality with compare(a, b) == 0, so "Alice Smith" counts as the same as "Ann Smith" and is not added (add returns false). Option A assumes equals is consulted. The fix is .thenComparing(p -> p.firstName): the ordering must be consistent with equals. Since a Comparator is supplied, Comparable is not needed (C is wrong).
Code Trace · Set Implementations
Question 7: Union and Intersection with Two Pointers
For the sorted arrays a = {2, 4, 6, 8} and b = {1, 4, 5, 8, 9}, what are the lists uni and both when the method finishes?
Both pointers start at the front. One pass, like merge in merge sort.
a[0] = 2 > b[0] = 1 → 1 is only in b: added to the union, j++.
a[0] = 2 < b[1] = 4 → 2 is only in a: added to the union, i++.
a[1] = b[1] = 4 → in both: added once to the union and to the intersection; both pointers advance.
a[2] = 6 > b[2] = 5 → 5 is added to the union, j++.
a[2] = 6 < b[3] = 8 → 6 is added to the union, i++.
a[3] = b[3] = 8 → in both; i = 4, j = 4.
i = 4 = a.length → the main loop ends; nothing is left in a. The intersection is complete: no more common elements are possible.
The 9 left in b goes straight into the union.
Result: uni = [1, 2, 4, 5, 6, 8, 9], both = [4, 8]. Each step advances at least one pointer → O(n + m).
void unionIntersection(int[] a, int[] b, List<Integer> uni, List<Integer> both) { int i = 0, j = 0; while (i < a.length && j < b.length) { if (a[i] < b[j]) uni.add(a[i++]); else if (a[i] > b[j]) uni.add(b[j++]); else { // in both uni.add(a[i]); both.add(a[i]); i++; j++; } } while (i < a.length) uni.add(a[i++]); while (j < b.length) uni.add(b[j++]); }
Multiple Choice · Set Implementations
Question 8: Hash or Tree?
For which of these operations is a hash-based set asymptotically slower than a set based on a balanced BST?
Correct: B. A hash table keeps no order, so finding the largest element ≤ x means scanning everything: O(n). A balanced BST does it in O(log n). contains/add are O(1)* with hashing and O(log n) in a BST. D is a distractor: union is O(n + m)* with hashing and O(m log(n + m)) with a BST, so hashing is still faster.
Short Answer · Set Implementations
Question 9: Set Algebra with Bit Vectors
What does the program print? Give the set each printed number represents.
int a = 0, b = 0; for (int x : new int[]{0, 2, 5, 6}) a |= 1 << x; for (int x : new int[]{2, 3, 6}) b |= 1 << x; System.out.println((a | b) + " " + (a & b) + " " + (a & ~b) + " " + Integer.bitCount(a));
Show answer
a = 0110 0101₂ = 101 ({0, 2, 5, 6}), b = 0100 1100₂ = 76 ({2, 3, 6}).
| Expression | Operation | Bits | Value | Set |
|---|---|---|---|---|
| a | b | union | 0110 1101 | 109 | {0, 2, 3, 5, 6} |
| a & b | intersection | 0100 0100 | 68 | {2, 6} |
| a & ~b | difference a − b | 0010 0001 | 33 | {0, 5} |
| bitCount(a) | |a| | four 1s | 4 | — |
Output: 109 68 33 4. The universe fits in 0..31, so each operation is a single machine instruction, O(1); for a larger universe, java.util.BitSet takes O(U / 64).
Short Answer · Multisets
Question 10: Multiset Counters
The program uses the Multiset class from the slides (an element → count map; remove deletes the entry when the count is 1). What does it print, and what is the final map?
Multiset<Character> m = new Multiset<>(); for (char c : "banana".toCharArray()) m.add(c); m.remove('a'); m.remove('b'); System.out.println(m.count('a') + " " + m.count('b') + " " + m.size() + " " + m.remove('x'));
Show answer
- After the insertions: {a:3, n:2, b:1}, size = 6 (repeats included).
remove('a'): count 3 → 2, size = 5.remove('b'): the count is 1, so the entry is deleted entirely (zero counts are not stored), size = 4.remove('x'):counts.getis null →false, size unchanged.
Output: 2 0 4 false; the map is {a:2, n:2}. count('b') returns 0 thanks to getOrDefault.
Had the same letters gone into a HashSet, the set would be {a, b, n} (size 3): a set records only presence, a multiset records multiplicity. Every operation is one hash access, O(1) on average.
Multiple Choice · Multisets
Question 11: Entries with Zero Counts
What happens if remove always writes counts.put(e, c - 1); instead of if (c == 1) counts.remove(e); else counts.put(e, c - 1);?
boolean remove(E e) { Integer c = counts.get(e); if (c == null) return false; counts.put(e, c - 1); // the change n--; return true; }
Correct: C. If the entry isn't deleted when its count reaches 0, the c == null check no longer detects "element absent": a second remove sets the count to −1 and corrupts size, and the number of distinct elements (counts.size()) grows too. A is a distractor: after the first remove, count really does return 0; the bug only shows up in later operations.
Multiple Choice · Disjoint Sets (Union-Find)
Question 12: The Naive Union Chain
Naive union(x, y) always puts root(x) under root(y) (no size or rank, no path compression). Starting from 5 singleton sets, we do union(1,2), union(2,3), union(3,4), union(4,5) in order. What is the tree's height, and how many links does find(1) follow?
Correct: D. At each step the root of the growing tree goes under the singleton node, giving the chain 1 → 2 → 3 → 4 → 5 of height n − 1 = 4; find(1) follows four links, O(n) in the worst case. A is the result of union by size: there, singletons join the root of the larger tree and the tree becomes a star (height 1).
Short Answer · Disjoint Sets (Union-Find)
Question 13: Union by Size
The letters A–H are mapped to indices 0–7 through a hash table. Unions are by size (the root of the smaller tree goes under the root of the larger; on a tie, the second root goes under the first), with no path compression. After the operations below, what is each letter's parent, and what is the tree's height?
union(A,B), union(C,D), union(E,F), union(C,G), union(A,C), union(E,H), union(E,B)
Show answer
- (A,B), (C,D), (E,F): equal sizes → B → A, D → C, F → E.
- (C,G): 2 > 1 → G → C (size of C = 3).
- (A,C): size 2 < 3 → A → C (size of C = 5).
- (E,H): 2 > 1 → H → E (size of E = 3).
- (E,B): find(B) = C (B → A → C); 3 < 5 → E → C (size of C = 8).
| A | B | C | D | E | F | G | H | |
|---|---|---|---|---|---|---|---|---|
| parent | C | A | C | C | C | E | C | E |
One set, root C; height 2 (B, F, H at depth 2). Union by size keeps the height ≤ log₂ 8 = 3.
Code Trace · Disjoint Sets (Union-Find)
Question 14: Rank and Path Compression
Starting with 6 elements (parent[i] = i, rank[i] = 0), what are the parent and rank arrays after the five union calls in the code?
union(0,1): roots 0 and 1, equal ranks (0) → parent[0] = 1, rank[1] = 1.
union(2,3): equal ranks → parent[2] = 3, rank[3] = 1.
union(4,5): equal ranks → parent[4] = 5, rank[5] = 1.
union(1,3): 1 and 3 are already roots, equal ranks (1) → parent[1] = 3, rank[3] = 2.
union(4,0): find(4): parent[4] = 5 ≠ 4 → find(5); 5 is a root. rx = 5 (parent[4] is already 5).
find(0): recursion along the path 0 → 1 → 3; 3 is the root.
Path compression on return: parent[0] changes from 1 to 3. 0 now links directly to the root; ry = 3.
rank[5] = 1 < rank[3] = 2 → 5, the root of the shorter tree, goes under 3: parent[5] = 3. No rank changes.
Result: parent = [3, 3, 3, 3, 5, 3], rank = [0, 1, 0, 2, 0, 1]; one set, root 3. 4 still links through 5; its path is compressed on the first find(4).
int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); return parent[x]; } boolean union(int x, int y) { int rx = find(x), ry = find(y); if (rx == ry) return false; if (rank[rx] < rank[ry]) parent[rx] = ry; else if (rank[rx] > rank[ry]) parent[ry] = rx; else { parent[rx] = ry; rank[ry]++; } return true; } // union(0,1); union(2,3); union(4,5); // union(1,3); union(4,0);
Multiple Choice · Disjoint Sets (Union-Find)
Question 15: Union-Find Complexity
m find/union operations on n elements use union by rank and path compression together. What is the total cost?
Correct: C (Tarjan, 1975). α(n) grows extremely slowly: it is ≤ 4 even for as many elements as there are atoms in the universe, so in practice it is constant. A is a distractor: that bound holds for union by rank/size alone (or path compression alone). D is wrong: the bound is amortized, and a single find can still take O(log n). Memory: the parent and rank arrays, O(n).
Short Answer · Disjoint Sets (Union-Find)
Question 16: Counting Connected Components
What does countComponents(8, new int[][]{{0,1}, {2,3}, {1,2}, {0,3}, {4,5}, {5,4}}) return? For which edges does union return false, and what does that mean?
int countComponents(int n, int[][] edges) { UnionFind uf = new UnionFind(n); int count = n; // n singleton sets for (int[] e : edges) if (uf.union(e[0], e[1])) count--; return count; }
Show answer
| Edge | (0,1) | (2,3) | (1,2) | (0,3) | (4,5) | (5,4) |
|---|---|---|---|---|---|---|
| union | true | true | true | false | true | false |
| count | 7 | 6 | 5 | 5 | 4 | 4 |
Returns 4; the components are {0, 1, 2, 3}, {4, 5}, {6}, {7}.
(0,3): 0 and 3 are already connected through (1,2), so the edge closes a cycle. (5,4): a repeat of the same edge. In both cases count is unchanged.
Total O(n + m·α(n)); as edges arrive later, the count updates immediately (dynamic connectivity).
Multiple Choice · Summary
Question 17: Changing an Element in a Set
The equals and hashCode methods of class Point use the fields x and y. What does the program print?
Set<Point> s = new HashSet<>(); Point p = new Point(1, 2); s.add(p); p.x = 5; System.out.println(s.contains(p) + " " + s.size());
Correct: A. p was placed in the bucket for the hash of (1, 2). When x changes, so does the hash code; contains(p) looks in the bucket of the new hash and doesn't find p. The element itself stays in the old bucket, so size remains 1. B is a distractor ("it's the same object"), but a HashSet finds objects through their bucket. Never change the fields that go into the hash of an object stored in a set.
Short Answer · Multisets
Question 18: Multimap Operations
The calls in the comments run in order. What do the three remove calls return, what is m at the end, and what is the multimap's size() (all (k, v) pairs)?
Map<String, List<String>> m = new HashMap<>(); void put(String k, String v) { m.computeIfAbsent(k, x -> new ArrayList<>()) .add(v); } boolean remove(String k, String v) { List<String> l = m.get(k); if (l == null || !l.remove(v)) return false; if (l.isEmpty()) m.remove(k); return true; } // put("CS203","Ann"); put("CS203","Ben"); // put("MATH101","Ann"); put("CS203","Ann"); // remove("CS203","Ann"); // remove("MATH101","Ann"); // remove("MATH101","Dana");
Show answer
- After the puts: CS203 → [Ann, Ben, Ann], MATH101 → [Ann]. On a key's first put,
computeIfAbsentcreates an empty list and returns it; afterwards it returns the existing list. remove("CS203","Ann")→ true:List.remove(Object)deletes only the first Ann → [Ben, Ann].remove("MATH101","Ann")→ true; the list is now empty, so the key is removed too.remove("MATH101","Dana")→ false:m.getreturns null.
Final: m = {CS203=[Ben, Ann]}, size = 2. Had the empty list not been removed, containsKey("MATH101") would still be true. put is O(1) on average, remove(k, v) O(1 + list length).
Multiple Choice · Multisets
Question 19: put Instead of computeIfAbsent
If the multimap's put is written as m.put(k, new ArrayList<>()); m.get(k).add(v);, what does m.get("A") return after put("A","x"), put("A","y"), put("B","z")?
Correct: B. Every put replaces the key's list with a new, empty one, so the list holding x is lost. A is what computeIfAbsent gives: a list is created only when the key is absent; otherwise the existing list comes back. C assumes the first list survives. D is wrong: right after the put, m.get(k) cannot be null; an NPE arises only if m.get(k).add(v) runs before any list was created.
Short Answer · Disjoint Sets (Union-Find)
Question 20: Tracing Quick-Find
A Quick-Find structure on 7 elements starts with id[i] = i. Using union(p, q) from the slides (it sets every entry equal to id[p] to id[q], and returns at once if they are equal), we call union(0,1), union(2,3), union(1,3), union(4,5), union(5,3), union(6,2), union(0,6) in order.
What is the id array after each call? In total, how many id[i] == pid comparisons and how many writes does the loop perform?
Show answer
| union | pid → qid | Writes | id |
|---|---|---|---|
| (0,1) | 0 → 1 | 1 | [1, 1, 2, 3, 4, 5, 6] |
| (2,3) | 2 → 3 | 1 | [1, 1, 3, 3, 4, 5, 6] |
| (1,3) | 1 → 3 | 2 | [3, 3, 3, 3, 4, 5, 6] |
| (4,5) | 4 → 5 | 1 | [3, 3, 3, 3, 5, 5, 6] |
| (5,3) | 5 → 3 | 2 | [3, 3, 3, 3, 3, 3, 6] |
| (6,2) | 6 → 3 | 1 | [3, 3, 3, 3, 3, 3, 3] |
| (0,6) | 3 = 3 | – | unchanged (return) |
Each of the first six unions scans the whole array: 6 · 7 = 42 comparisons; writes: 1 + 1 + 2 + 1 + 2 + 1 = 8. In (6,2), qid is id[2] = 3, not 2. find is O(1), but each union is O(n): n − 1 unions take O(n²).
Code Trace · Disjoint Sets (Union-Find)
Question 21: Tracing the Iterative find
With parent = [3, 0, 1, 6, 6, 4, 6], we call find(2). What does it return, and what is parent afterwards?
Start with root = x = 2. The first loop finds the root; the second compresses the path.
parent[2] = 1 ≠ 2 → root = 1.
parent[1] = 0 ≠ 1 → root = 0.
parent[0] = 3 ≠ 0 → root = 3.
parent[3] = 6 → root = 6; since parent[6] = 6, the root is found: the path is 2 → 1 → 0 → 3 → 6.
parent[2] = 1 ≠ 6 → save next = 1, then parent[2] = 6.
x = 1: parent[1] = 0 ≠ 6 → next = 0, parent[1] = 6.
x = 0: parent[0] = 3 ≠ 6 → next = 3, parent[0] = 6.
x = 3: parent[3] = 6 = root → the loop ends; 3 already linked to the root.
return 6. Nodes 2, 1, 0 on the path now link to the root. Node 5, off the path (5 → 4 → 6), is unchanged; its path is compressed by its own find.
int find(int x) { int root = x; while (parent[root] != root) root = parent[root]; while (parent[x] != root) { // path compression int next = parent[x]; parent[x] = root; x = next; } return root; } // parent = [3, 0, 1, 6, 6, 4, 6]; find(2)