Your single 2–3 day route
🔴 Exam-core The revision tutorials name the likely families; past exams show the examiner changes data, code, and wording. Learn a method you can execute on a fresh instance.
What the evidence says
- ID3: revision says “definitely”; repeated manual calculations across tutorials/exams.
- Quantum: appeared in the last three exams with different questions; revision says prepare broadly.
- New 2026 scope: selection, median-of-medians, Akra–Bazzi, probing and bucket sort were explicitly added/emphasized.
- Latest pattern: choose 2 of 3 large problems; a problem mixes theory, calculation, code, and explanation.
How to use the Casio
Use the calculator for arithmetic, not as a substitute for written setup. For log₂x, enter ln(x) ÷ ln(2). Store repeated entropy terms in variables if useful. Keep at least 6 internal digits; round only the displayed final gain/probability to 3–4 decimals.
Sanity checks: entropy is in [0,1] for a binary label; information gain cannot be negative except tiny rounding; Bloom probability lies in [0,1]; integer k requires checking both neighbors.
Write answers the examiner can award
2 of 3
The latest supplied exam says unsupported answers receive no credit. For every theorem or formula, write: name → parameters → applicability check → substitution → result → interpretation/check. For code: state the invariant, handle the empty/boundary case, update every pointer/size field, then give time and auxiliary-space complexity.
Suggested timing: 4 minutes scan and choose; 38 minutes per problem; 10 minutes check. If a calculation stalls, leave your setup—correct method earns partial credit.
Complexity: translate code into growth
🔴 Exam-core Complexity is the language used in almost every other chapter. Count how the dominant operation scales with input size; ignore hardware time and lower-order clutter.
Bounds you must distinguish
f(n) ∈ Ω(g(n)) : eventual lower bound
f(n) ∈ Θ(g(n)) : both; tight bound
f(n) ∈ o(g(n)) : strictly slower growth
Õ(g(n)) : hides logarithmic factors
Selection cue
If asked for “worst-case”, report an upper bound, preferably tight. If asked for “running time complexity” and you can prove both sides, use Θ. Big-O is a set, not an equals sign.
Origins: n is the input measure chosen in the question; c,n₀ are witnesses in the definition, not data values.
Worked: binary search
Each comparison keeps only half the interval: after t iterations, remaining size is n/2ᵗ. Stop when n/2ᵗ ≤ 1, so t ≥ log₂n. Thus time is Θ(log n).
Iterative binary search stores only indices: Θ(1) auxiliary space. Recursive binary search has depth Θ(log n), so Θ(log n) call-stack space.
Check: doubling n adds one iteration.
Worked: nested loops are not automatically n²
for i in range(n):
j = 1
while j <= i:
j *= 2For fixed i, the inner loop runs ⌊log₂i⌋+1 times. Sum: Σᵢ log i = log(n!) = Θ(n log n). State the varying bound; do not multiply two “n loops” mechanically.
Growth order to know
1 ≺ log log n ≺ log n ≺ √n ≺ n ≺ n log n ≺ n² ≺ n³ ≺ cⁿ ≺ n! for constant c>1. Log bases differ only by a constant, so they are identical in Θ-notation.
🟠 Exam-practical Pseudo-polynomial: a running time polynomial in a numeric value but exponential in its bit-length. Example: looping to value W is O(W), but the input stores W in only Θ(log W) bits.
⚔ From the real exams — drill it
The search-complexity ladder opened the most recent exam. Same ladder, new story, every year.
O(n) — unsorted, so worst case checks every element of S once (linear scan; here n = 10⁹).
Two things are being graded: the bound and the one-line reason. “Unsorted ⇒ nothing rules out any element ⇒ must compare against each in the worst case.” Name n (|S|). Don't write more — 2 points, two sentences maximum.
naive lookup unsorted: O(n) — one line in your search ladder (below).Binary search: O(log n) — halves the range each step.
Balanced search tree: O(log n) — height kept logarithmic.
Hash table: expected O(1), worst O(n) — all keys can collide into one bucket.
Each bound + its 4-to-8-word reason. The hash-table line is the trap: the question explicitly asks for both expected and worst case — leaving out O(n) costs the point. If asked what makes the expected case hold: a good hash function spreads keys uniformly, load factor kept constant by resizing.
lookup: naive O(n) · binary O(log n) (sorted!) · balanced BST O(log n) · hash exp O(1)/worst O(n) · Bloom O(k)=O(1), FP possible, no FNThe structures spill to disk → every probe can become a disk access, orders of magnitude slower (swapping/paging). Asymptotics unchanged, real time dominated by I/O. Practical fix: disk-aware structures (e.g. B-trees) or a small in-RAM prefilter (Bloom filter, next part).
The examiner wants you to separate the model (O-notation counts comparisons) from the machine (a random disk access costs ~10⁵× a RAM access). Naming either B-trees or the Bloom filter as mitigation shows you see where the problem is heading — (e) asks for the Bloom filter explicitly.
no RAM: disk I/O dominates → B-tree / Bloom prefilterRecursion, recurrences and dynamic programming
🔴 Exam-core A recurrence models total work by separating recursive calls from non-recursive work. Always define the base case and what n measures.
Master theorem — a decision procedure
critical term = n^(log_b a)
- Read a = number of equal-size calls, b = shrink factor, and f(n) = combine/non-recursive work.
- Compute c=log_ba=ln(a)/ln(b).
- Compare f(n) with nᶜ. Polynomially smaller → Case 1, same up to log factors → Case 2, polynomially larger plus regularity → Case 3.
| Case | Condition | Conclusion |
|---|---|---|
| 1 | f(n)=O(n^(c−ε)) | T(n)=Θ(nᶜ) |
| 2 | f(n)=Θ(nᶜ logᵏn), k≥0 | T(n)=Θ(nᶜ log^(k+1)n) |
| 3 | f(n)=Ω(n^(c+ε)) and af(n/b)≤qf(n), q<1 | T(n)=Θ(f(n)) |
Assumptions: subproblems have the same fraction n/b; floors/ceilings do not change the asymptotic answer; f is eventually positive. If sizes differ, use Akra–Bazzi.
Case 1
T(n)=8T(n/2)+n². Here c=log₂8=3; n²=O(n^(3−1)). Therefore T(n)=Θ(n³).
The leaves dominate.
Case 2
T(n)=2T(n/2)+n. Here c=1, f(n)=Θ(nᶜ), k=0. Therefore T(n)=Θ(n log n).
Every tree level costs n; there are log₂n levels.
Case 3
T(n)=2T(n/2)+n². c=1; n²=Ω(n^(1+1)). Regularity: 2(n/2)²=n²/2. Thus Θ(n²).
The root dominates.
Substitution: a recurrence the revision slide mishandles
For T(n)=4T(n−1)+1, expand powers carefully:
=4ᵏT(n−k)+(4ᵏ−1)/3
Set k=n−1 to hit T(1). Hence T(n)=Θ(4ⁿ). The source expansion drops exponents; this is a recorded correction, not a new theorem.
Top-down DP
Write the recursive definition first; cache by the full state. Each distinct state is solved once.
def fib(n, memo={}):
if n < 2: return n
if n not in memo:
memo[n] = fib(n-1,memo)+fib(n-2,memo)
return memo[n]Θ(n) time, Θ(n) cache plus stack.
Bottom-up DP
Order states so every dependency already exists.
def fib(n):
if n < 2: return n
a, b = 0, 1
for _ in range(2, n+1):
a, b = b, a+b
return bΘ(n) time, Θ(1) auxiliary space.
⚔ From the real exams — drill it
The “Deggendorf numbers” block is the professor's favorite recursion vehicle — it has appeared, with the same definition, in at least four exams. Learn the five-part chain once and it is 25 nearly-free points.
def deg(n):
if n < 3:
return 1
return deg(n-1) + deg(n-2) + deg(n-3)“Naive” means: translate the mathematical definition literally, base case first. n < 3 covers exactly n ∈ {0, 1, 2}. Nothing else is wanted here — memoization comes later in the problem, and writing it now answers the wrong subpart.
Each call makes 3 recursive calls; the recursion tree has depth ≈ n.
T(n) = T(n−1) + T(n−2) + T(n−3) + O(1) ≤ 3T(n−1) + O(1)
→ tree of ≤ 3n nodes, O(1) work each.
“Derive” = show the recurrence and the tree argument, not just say “exponential”. The three-line chain (recurrence → bound by the 3-ary tree → node count × constant work) is exactly the supported reasoning the cover sheet demands. The true growth rate is the tribonacci constant ≈ 1.84ⁿ; O(3ⁿ) as an upper bound with the tree argument is the expected full-marks answer.
3-branch recursion → T ≤ 3T(n−1)+O(1) → O(3^n); k-branch → O(k^n)deg(4) ├─ deg(3) │ ├─ deg(2)=1 │ ├─ deg(1)=1 │ └─ deg(0)=1 ├─ deg(2)=1 └─ deg(1)=1 result: deg(4) = 3+1+1 = 5
“Complete” means every call node, including the three base-case leaves under deg(3). Adding the returned values (deg(3)=3, so deg(4)=5) is the free sanity check that shows the tree is right. Note deg(2) appears twice — that duplicated subtree is precisely the argument for part (d)'s memoization.
deg: 1,1,1,3,5,9,17… (values from n=0 — lets you verify any tree instantly)def deg(n, memo=None):
if memo is None:
memo = {}
if n < 3:
return 1
if n not in memo:
memo[n] = deg(n-1, memo) + deg(n-2, memo) + deg(n-3, memo)
return memo[n]
Each deg(i) computed once → O(n) time, O(n) space.
Top-down = keep the recursive shape, add a cache. “Without auxiliary tools” bans lru_cache — a hand-rolled dict is exactly what's wanted. The memo=None default avoids Python's mutable-default-argument trap (a shared dict across calls) — mentioning that shows real understanding. Always end a DP answer with the new complexity: that's where the 8 points close.
Bottom-up variant (asked in 2020–21): a=b=c=1, loop for _ in range(3, n+1): a, b, c = b, c, a+b+c, return c — O(n) time, O(1) space.
def f(n, memo=None): if memo is None: memo={} · if base: return … · if n not in memo: memo[n]=… · return memo[n] + bottom-up: rolling vars, O(1) spacefunctools.lru_cache — a decorator that caches results keyed by the call arguments; repeated calls return the cached value.
from functools import lru_cache
@lru_cache(maxsize=None)
def deg(n):
if n < 3:
return 1
return deg(n-1) + deg(n-2) + deg(n-3)Name the tool, one sentence on what it does, then the naive code with the decorator on top — that is the whole answer. (Your old handwritten sheet had exactly this import line at the top. It stays.)
from functools import lru_cache · @lru_cache(maxsize=None)def binary_search(data, target):
low, high = 0, len(data) - 1
while low <= high:
mid = (low + high) // 2
if data[mid] == target:
return True
elif data[mid] < target:
low = mid + 1
else:
high = mid - 1
return False
(b) Recursive: O(log n) auxiliary space — one stack frame per halving.
Iterative: O(1) — three variables, no growing state.
This is your handwritten sheet's binary-search block, verbatim — it survives to the new sheet. The space question is the actual test: recursion isn't free, each nested call holds a frame until it returns, and binary search nests Θ(log n) deep. The iterative rewrite eliminates exactly that. Say “auxiliary space” — the input itself doesn't count.
space: recursive O(log n) stack, iterative O(1)def sign(x):
if x < 0:
return -1
elif x == 0:
return 0
return 1
def bisection(f, a, b, tol, max_iterations):
if sign(f(a)) == sign(f(b)):
raise ValueError('f(a) and f(b) must differ in sign')
for _ in range(max_iterations):
m = (a + b) / 2
if abs(f(m)) < tol: # tolerance: |f(m)| < tol → converged
return m
if sign(f(m)) == sign(f(a)):
a = m # root is in right half
else:
b = m # root is in left half
return (a + b) / 2
Tolerance used as: stop when |f(m)| < tol (function value close enough to 0).
Bisection is binary search on a continuous function: the invariant is sign(f(a)) ≠ sign(f(b)), so the interval always contains a root (intermediate value theorem — name it if asked for justification). The loop keeps the invariant by replacing the boundary whose sign matches the midpoint's. The hints in the question are a grading checklist: (1) they want the sign-preserving update shown, (2) they want your tolerance convention stated in words — either |f(m)| < tol or interval width (b−a)/2 < tol is accepted if you say which. The bound check at the top plus max_iterations as a fail-safe are the remaining marks.
invariant: sign(f(a))≠sign(f(b)); state tol convention!def min_max1(data):
mn = mx = data[0]
for v in data[1:]:
if v < mn: mn = v
if v > mx: mx = v
return mn, mx
def min_max2(data, i=0): # signature rethought: index parameter
if i == len(data) - 1:
return data[i], data[i]
mn, mx = min_max2(data, i + 1)
return min(data[i], mn), max(data[i], mx)“Rethink the signature” is the hint that pure min_max2(data) can't recurse cleanly — add an index (or slice, but slicing copies O(n) per level, worth a remark). Pattern: solve the rest recursively, combine with the current element. Both return a tuple — matching “returns both” in the statement.
recursion pattern: base = last element · combine = min/max(current, rest)Sorting and deterministic selection
🔴 Exam-core Know the properties, trace one pass, and derive rather than memorize where possible. Selection asks for the k-th order statistic without fully sorting.
| Algorithm | Best | Average | Worst | Aux. space | Stable? | In place? |
|---|---|---|---|---|---|---|
| Bubble (early stop) | Θ(n) | Θ(n²) | Θ(n²) | Θ(1) | Yes | Yes |
| Insertion | Θ(n) | Θ(n²) | Θ(n²) | Θ(1) | Yes | Yes |
| Merge | Θ(n log n) | Θ(n log n) | Θ(n log n) | Θ(n) | Yes* | No* |
| Quicksort | Θ(n log n) | Θ(n log n) | Θ(n²) | Θ(log n) avg stack | No | Usually |
| Heap | Θ(n log n) | Θ(n log n) | Θ(n log n) | Θ(1) | No | Yes |
| Bucket | Θ(n+k) | Θ(n+k) expected under a uniform-distribution assumption | Θ(n²) | Θ(n+k) | Depends | No |
*For the standard course implementations. Source: Slides, pp. 339–382; Sorting tutorial, pp. 2–27.
Merge sort from its recurrence
Two half-size recursive sorts plus linear merging:
Master: a=2,b=2,c=1,f(n)=Θ(n); Case 2 gives Θ(n log n). Stability comes from choosing the left item first on equal keys.
Comparison sorting lower bound is Ω(n log n), so merge sort is asymptotically optimal in that model.
Quicksort: explain the bad pivot
If the pivot is always smallest/largest, partitions are sizes 0 and n−1:
Random pivots make expected partitions sufficiently balanced, yielding expected Θ(n log n). This is expected, not guaranteed.
Stability in one sentence
A stable sort preserves the input order of records with equal keys. Test with decorated records: (2,A),(1,X),(2,B) must end as (1,X),(2,A),(2,B). Stability matters when sorting by multiple fields in stages.
Median of medians — complete source exercise
Question: find the 11th smallest in [31,8,22,4,17,29,13,2,26,11,15,6,19,1,24,7,28,10,20,3,27,9]; equivalently zero-based rank k=10.
- Groups of five: [31,8,22,4,17]→17, [29,13,2,26,11]→13, [15,6,19,1,24]→15, [7,28,10,20,3]→10, leftover [27,9]→9 using the source’s lower-median convention.
- Medians are [17,13,15,10,9]; their median is pivot 13.
- Partition original data: less has 10 values [8,4,2,11,6,1,7,10,3,9]; equal has 1; greater has 11.
- Because zero-based k=10 equals |less|, the equal partition contains the target. Answer: 13.
Independent check: sorted order begins 1,2,3,4,6,7,8,9,10,11,13,…, so the 11th value is 13.
Why median-of-medians is linear
Group into fives, recursively select the median of the group medians, partition, and recurse only on the side containing rank k. At least about 3n/10 elements are discarded (up to constants), so the worst recurrence is:
Master theorem does not apply because subproblem sizes differ. Akra–Bazzi yields an exponent below 1 for the recursive part, while the linear partition integral makes the total Θ(n).
⚔ From the real exams — drill it
Every summer exam has a 25-point sorting problem, and the merge function has been asked, verbatim, at least three times.
def merge(S1, S2, S):
i = j = 0
while i + j < len(S):
if j == len(S2) or (i < len(S1) and S1[i] < S2[j]):
S[i+j] = S1[i]
i += 1
else:
S[i+j] = S2[j]
j += 1The one condition line carries the whole function: take from S1 when S2 is exhausted (j == len(S2)) or S1's front element is smaller (guarded by i < len(S1) so we never overrun). i + j is both the write position and the loop bound — the invariant is “S[0 : i+j] holds the i+j smallest elements, merged”. This is the course's own implementation (it was on your handwritten sheet, with the same short-circuit trick) — reproduce it exactly; inventing a different merge risks losing the “as discussed in class” marks.
def merge_sort4(S):
n = len(S)
if n < 2:
return
q, m, p = n//4, n//2, 3*n//4
S1, S2, S3, S4 = S[:q], S[q:m], S[m:p], S[p:]
merge_sort4(S1); merge_sort4(S2)
merge_sort4(S3); merge_sort4(S4)
T1, T2 = S1 + S2, S3 + S4 # right-sized containers
merge(S1, S2, T1)
merge(S3, S4, T2)
merge(T1, T2, S)
(c) Recurrence: T(n) = 4T(n/4) + O(n).
Master theorem (applicable: a=4 ≥ 1, b=4 > 1, f(n)=O(n¹), d=1): log₄4 = 1 = d → case 2.
(b): split into four quarters, sort each recursively, then merge pairwise — the statement says you may reuse two-list merge, so three merge calls (S1+S2, S3+S4, then the halves) is the intended shape. T1, T2 = S1+S2, S3+S4 just creates containers of the right length for merge to overwrite.
(c): the derivation must name the recurrence, then the theorem with the applicability check — the cover sheet's “fundamental theorem” rule. Per level all merges touch each element a constant number of times → O(n) per level; depth log₄n; log₄n = log₂n / 2 — a constant factor, so identical asymptotics. That sentence is (d)'s answer too: no asymptotic benefit (possibly fewer recursion levels, but more merge passes per level — constants shuffle, Θ(n log n) stays).
k-way merge sort: T(n)=kT(n/k)+O(n) → O(n log n) ∀k — log base = constant factorLower bound: Ω(n log n) comparisons — a comparison sort must distinguish n! orderings; a decision tree of height h has ≤ 2h leaves, so h ≥ log₂(n!) = Θ(n log n).
Yes — non-comparison sorts beat it under assumptions: counting/bucket sort O(n+k), radix O(d·n) for bounded integer keys.
Both halves are required: the bound with the decision-tree sentence, and the escape hatch (“yes, if we don't only compare”). Naming one non-comparison sort with its complexity and its assumption (keys from a bounded/integer universe) completes it.
comparison sort ≥ log₂(n!) = Ω(n log n); counting/bucket O(n+k), radix O(dn) — need bounded keys(a) Stable: bubble, insertion, merge. (Not stable: selection, quicksort, heapsort.)
(b) Stable = equal keys keep their input order. Example: sorting [(2,'a'), (1,'x'), (2,'b')] by number → stable gives (2,'a') before (2,'b'); an unstable sort may swap them. Matters when sorting by a second key after a first.
“Names only” questions are pure sheet lookups — this is why the sorting table belongs on your A4 side. For the explanation: definition in one sentence + a two-element tie example + one “why it matters” clause. Done in four lines.
def bubble_sort(data):
n = len(data)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if data[j] > data[j+1]:
data[j], data[j+1] = data[j+1], data[j]
swapped = True
if not swapped:
return # already sorted — stop
Best case (already sorted): one pass, no swaps → O(n); worst stays O(n²).
The swapped flag is the entire trick: a pass with zero swaps proves sortedness, so bail out. State both the new best case and that the worst case is unchanged — the comparison is what the subpart is really asking for.
bubble + swapped flag → best O(n) (one line under the sorting table)Lists, stacks, queues, iterators and generators
🔴 Exam-core The latest exam makes this a full problem. Pointer diagrams are executable specifications: draw the nodes, identify the predecessor, change links in a safe order, then update size.
Array list
- Θ(1) indexing.
- Append is amortized Θ(1); an individual resize is Θ(n).
- Middle insert/delete is Θ(n) due to shifting.
- Compact memory and cache-friendly traversal.
Linked list
- Θ(n) access by index.
- Θ(1) insert/delete given the relevant node/predecessor.
- No contiguous reallocation; nodes can grow one at a time.
- Pointer overhead and weaker locality.
Latest exam Problem 1 — full code and reasoning
class LinkedList:
class Node:
def __init__(self, element):
self.element = element
self.next_element = None
def __init__(self):
self.head = None
self.size = 0
# (a)
def add_middle(self, element):
# Odd length has no single gap at the middle: leave unchanged.
if self.size % 2 == 1:
return False
new = self.Node(element)
if self.size == 0:
self.head = new
else:
prev = self.head
for _ in range(self.size // 2 - 1):
prev = prev.next_element
new.next_element = prev.next_element
prev.next_element = new
self.size += 1
return True
# (b)
def remove_first(self):
if self.head is not None:
self.head = self.head.next_element
self.size -= 1
# (d)
def __iter__(self):
current = self.head
while current is not None:
yield current.element
current = current.next_element
(a) Why middle is correct: for even size 2r, the new node is inserted after the first r nodes. The loop stops on index r−1. Empty list is even and inserts at the head. An odd list returns False with no mutation.
(c) Two benefits each: linked lists support Θ(1) insertion/deletion when the predecessor is known and grow without contiguous reallocation; array lists provide Θ(1) indexing and compact, cache-friendly storage.
Complexities for (a), (b), and (d): add_middle Θ(n) time/Θ(1) extra space; remove_first Θ(1); iteration Θ(n) total and Θ(1) generator state.
Trace tests: []→[x]; odd [x] unchanged; [a,b]→[a,x,b]; remove until empty; iteration yields every value once. A generator is lazy: each yield suspends state until the next request.
ADT transfer cues
Stack: LIFO; push/pop/top at one end, all Θ(1). Queue: FIFO; enqueue rear/dequeue front Θ(1) with a deque or linked endpoints. Deque: both ends. If an array queue shifts elements on every dequeue, it is accidentally Θ(n); use a circular buffer/head index.
⚔ From the real exams — drill it
The most recent resit spent a full 25-point problem on linked-list surgery; MyArray methods recur across exams. Pointer updates and size bookkeeping are where the marks leak.
def add_middle(self, element):
if self._size % 2 != 0:
raise ValueError('only possible for even number of elements')
node = self._Node(element)
if self._size == 0:
self._head = node
else:
cur = self._head
for _ in range(self._size // 2 - 1):
cur = cur._next_element
node._next_element = cur._next_element
cur._next_element = node
self._size += 1Grading checklist for any list-mutation method: (1) the odd case — the statement explicitly demands “handle it correctly”, and raising a clear error is correct handling; (2) the empty list (size 0 is even — the new node becomes the head); (3) walk exactly size/2 − 1 hops so cur is the node before the middle; (4) link the new node before re-pointing cur — the classic order mistake loses the tail; (5) update _size. Five checkpoints ≈ 9 points.
list insert after cur: node.next = cur.next; cur.next = node (this order!) · always ±size · walk k hops → node k+1def remove_first(self):
if self._head is None:
return
self._head = self._head._next_element
self._size -= 1
(c) Linked list: O(1) insert/remove at head; grows without resizing (no capacity waste).
Array list: O(1) random access by index; contiguous memory → cache-friendly.
(b) is four lines, but two of them are the graded ones: the empty-list guard and the size decrement. The removed node needs no explicit freeing — Python's GC collects it once unreferenced (say this if asked). (c) wants two benefits each — symmetric answers; complexity-flavored phrasing (O(1) head ops vs O(1) indexing) is the safest currency.
linked: O(1) head ins/del, no resize · array: O(1) index, cache-local; append O(1) amortizeddef __iter__(self):
cur = self._head
while cur is not None:
yield cur._element
cur = cur._next_elementThe usage snippet (for value in lst:) tells you the method name: Python's for-loop protocol calls __iter__. Any function containing yield is a generator — Python builds the iterator object for you, state (the paused position) included. Yield the element, not the node. This same 5-liner answered the 2023 Summer exam too — it recycles.
__iter__ generator, verbatim.def insert(self, k, value):
if k < 0:
k += self._n # negative index → from the end
k = max(0, min(k, self._n)) # clamp like list.insert
if self._n == self._capacity:
self._resize(2 * self._capacity)
for i in range(self._n, k, -1):
self._A[i] = self._A[i-1] # shift right, back to front
self._A[k] = value
self._n += 1Four graded moves: normalize the negative index (k += n — the explicit requirement); grow when full (doubling, matching the class design — this is why append is O(1) amortized); shift from the back so nothing is overwritten; write + increment size. If the variant asks for __setitem__ instead (2023–24 resit): same negative-index normalization, then bounds-check and assign — no shifting.
MyArray: neg idx → k+=n · full → _resize(2·cap) · shift n→k backwards · doubling ⇒ append O(1) amortizedGraphs and shortest paths
🟠 Exam-practical Representations and “frontier” meaning recur in exams. Dijkstra is the lowest-cost algorithm for non-negative edge weights; BFS is shortest path only in unweighted/equal-weight graphs.
Adjacency list
Space Θ(|V|+|E|). Iterating neighbors of v costs Θ(deg(v)). Best for sparse graphs.
Adjacency matrix
Space Θ(|V|²). Edge query is Θ(1); scanning all neighbors is Θ(|V|). Symmetric for undirected graphs.
Dijkstra by hand
Maintain tentative distance d[v] and predecessor. Initialize d[A]=0, all others ∞. Repeatedly settle the unvisited vertex with minimum tentative distance and relax each outgoing edge:
| Settle | d(A) | d(B) | d(C) | d(D) | Change |
|---|---|---|---|---|---|
| start | 0 | ∞ | ∞ | ∞ | — |
| A | 0 | 3 | 1 | ∞ | B←A, C←A |
| C | 0 | 3 | 1 | 4 | D←C; B candidate 3 ties |
| B | 0 | 3 | 1 | 4 | D candidate 8, no |
| D | 0 | 3 | 1 | 4 | done |
Output: distances A=0,B=3,C=1,D=4; path to D by predecessors is A→C→D. With binary heap and adjacency lists: O((|V|+|E|)log|V|), often written O(|E|log|V|) for connected sparse graphs.
Assumption check: all edge weights are non-negative. Negative edges invalidate the settle-once proof.
Frontier
The frontier is the collection of discovered but not yet expanded states, plus the operation used to choose the next one. Queue → BFS; stack → DFS; min-priority queue by path cost → Dijkstra; by g+h → A*. The choice changes the search order and guarantees.
⚔ From the real exams — drill it
The graph opener (formal definition + adjacency matrix) and the frontier/deque argument together appear in over half of all past exams.
Directed + weighted → 3-tuple G = (V, E, w):
V = {1,2,3,4}
E = {(1,2), (2,1), (2,3), (3,4), (4,3)}
w(1,2)=3, w(2,1)=2, w(2,3)=4, w(3,4)=1, w(4,3)=2
A = to: 1 2 3 4 (row = from, entry = weight, 0 = no edge) from 1 [ 0 3 0 0 ] from 2 [ 2 0 4 0 ] from 3 [ 0 0 0 1 ] from 4 [ 0 0 2 0 ]
“k-tuple” is a prompt to count what the graph has: nodes and edges always (V, E); weights add w: E→ℝ; the graph is directed, so E contains ordered pairs and A need not be symmetric (here it isn't: A[1][2]=3 but A[2][1]=2 — different arcs). Read each arrowhead carefully: the two arcs of a node pair are separate directed edges with separate weights. State your row-convention (“row = from”) — either convention is accepted if declared.
G=(V,E) · +weights: (V,E,w), w:E→ℝ · digraph: ordered pairs, A asym · row=from (declare!) · space: matrix O(V²), adj list O(V+E)(b) Line 09 frontier.pop(0) on a Python list is Θ(n) — every remaining element shifts left one slot. Done once per expanded path → up to Θ(n²) total shifting. The frontier is used FIFO (append at back, take from front) → the right ADT is a queue, e.g. collections.deque: popleft() and append() are O(1).
(c) Rewrite two lines:
07: frontier = deque([ [start] ]) 09: path = frontier.popleft()
The question names its own rubric: use O and Θ. Θ(n) for pop(0) is the precise claim (it always shifts, not just in the worst case); O(1) for the deque ops. Then say why a queue is the right ADT: breadth-first order = first discovered, first expanded. In (c), reference the line numbers explicitly (“rewriting lines 07 and 09”) — the statement demands it — and change nothing else; append on line 18 already works on a deque.
list.pop(0) Θ(n) → deque.popleft() O(1) · frontier = discovered, not yet expanded · BFS queue / DFS stack / Dijkstra min-heapTrees, traversal and ID3 decision learning
🔴 Exam-core ID3 is the strongest explicit hint. Treat it as an accounting problem: parent entropy minus the weighted entropy remaining after a split.
Traversal
Preorder: node, children. Inorder (binary): left, node, right. Postorder: children, node. Level order: BFS queue. A complete traversal is Θ(n); recursive auxiliary space is Θ(h), which is Θ(log n) when balanced and Θ(n) when degenerate.
Balanced search trees
Balance keeps height h=Θ(log n), making search/insert/delete Θ(log n). An unbalanced BST can become a chain with h=n−1 and Θ(n) operations.
Entropy and information gain
Remainder(A)=Σᵥ |Sᵥ|/|S| · H(Y in Sᵥ)
IG(Y,A)=H(Y)−Remainder(A)
Meaning: entropy is label uncertainty in bits; remainder is expected uncertainty after learning attribute A; gain is uncertainty removed. pᵢ comes from label counts; Sᵥ is the subset with attribute value v. There are no physical units.
Cue/order: when asked to “learn a tree with ID3”, (1) count labels, (2) compute parent H, (3) for every candidate attribute make a branch count table, (4) compute weighted remainder and gain, (5) choose maximum gain, (6) recurse on impure branches. State how ties are broken.
Latest exam Problem 2(a) — every intermediate step
Labels: 2 positive, 4 negative. Parent entropy:
| Attribute | Branch label counts (+,−) | Weighted remainder | Gain |
|---|---|---|---|
| sky | cloudy (0,1), sunny (2,1), rainy (0,2) | (1/6)0+(3/6)H(2/3,1/3)+(2/6)0 = 0.459148 | 0.459148 |
| air | warm (2,3), cold (0,1) | (5/6)H(2/5,3/5)=0.809125 | 0.109170 |
| humid | normal (0,2), high (2,2) | (2/6)0+(4/6)1=0.666667 | 0.251629 |
| wind | strong (2,2), weak (0,2) | (4/6)1+(2/6)0=0.666667 | 0.251629 |
Root: sky has maximum gain. cloudy→−, rainy→−. For sunny rows, labels are +,+,−. Air is constant; humid and wind each separate perfectly: humid=high→+, humid=normal→−, or equivalently wind=strong→+, wind=weak→−. State a deterministic tie rule; choose humid.
Exam-ready ending: “Choose sky because IG=0.4591 is maximal. Recurse only on sunny. Humid and wind tie with perfect gain; using humid as the stated tie-break gives the shown tree.”
⚔ From the real exams — drill it
ID3 is the single most reliable block of points in this course — a manual entropy/gain calculation appears in nearly every exam, worth 5–18 points. Two real instances below, both fully worked and machine-verified.
def is_symmetric(node):
def mirror(left, right):
if left is None and right is None:
return True
if left is None or right is None:
return False
return (mirror(left.left, right.right) and
mirror(left.right, right.left))
if node is None:
return True
return mirror(node.left, node.right)Symmetry is a statement about two subtrees mirroring each other, so the recursion needs a two-argument helper: outer-vs-outer (left.left vs right.right) and inner-vs-inner. The two base cases carry points: both empty → symmetric; exactly one empty → not. This is verbatim from your old handwritten sheet — it was right then and it answered a real exam since. For the marking subpart (a): a tree is symmetric iff this mirror test passes; single-child nodes break symmetry unless mirrored on the other side.
mirror(l,r) helper — 3 lines of logic: both None→T · one None→F · mirror(l.l,r.r) and mirror(l.r,r.l)def sum(self):
result = self.data
if self.left is not None:
result += self.left.sum()
if self.right is not None:
result += self.right.sum()
return resultThe universal tree-recursion template: my value + recurse into whichever children exist. The is not None guards are the graded detail — calling None.sum() crashes. O(n) time (each node once), O(h) stack. The same skeleton answers count, height (1 + max(...)), find-max — one template, many exam questions. (Also on your old sheet — kept.)
tree recursion: val = self.data (+ left.f() if left) (+ right.f() if right) — guard None!Labels: 4+, 2− → H(S) = −(4/6)log₂(4/6) − (2/6)log₂(2/6) = 0.918
Gain(S,a) = H(S) − Σ_v (|S_v|/|S|)·H(S_v) sky: sunny(3+,1−) rainy(1+,1−) → rem = (4/6)·0.811+(2/6)·1 = 0.874 → G=0.044 air: warm(4+,1−) cold(0,1−) → rem = (5/6)·0.722+(1/6)·0 = 0.601 → G=0.317 humid: normal(1+,1−) high(3+,1−) → rem = (2/6)·1+(4/6)·0.811 = 0.874 → G=0.044 wind: strong(4+,1−) weak(0,1−) → rem = (5/6)·0.722+(1/6)·0 = 0.601 → G=0.317
“Write out intermediate steps” = the rubric: H(S), then per attribute the split counts, the weighted remainder, the gain. The layout above (one line per attribute) is the fastest complete form. This instance contains a deliberate tie — air and wind produce identical splits ((4+,1−)/(0,1−)); saying “tie — either is a valid root” is the mark-winning observation, silently picking one without comment is not. Calculator: log₂x = ln x ÷ ln 2; store H-values you reuse (0.811 = H(3,1), 0.722 = H(4,1), 1 = H(1,1)) — they recur constantly.
H(S)=−Σp·log₂p · Gain = H − Σ(|Sv|/|S|)H(Sv) · log₂x = ln x/ln 2 + the mini-table H(1,1)=1 · H(3,1)=.811 · H(4,1)=.722 · H(4,2)=.918 · H(5,1)=.650Labels: 2+, 4− → H(S) = 0.918.
sky: cloudy(0,1−) sunny(2+,1−) rainy(0,2−)
rem = (1/6)·0 + (3/6)·0.918 + (2/6)·0 = 0.459 → G = 0.459 ← max
air: G = 0.109 humid: G = 0.252 wind: G = 0.252
Root = sky. Branches: cloudy → all −, leaf “−”; rainy → all −, leaf “−”.
sunny = {2:+, 4:+, 5:−}, H = 0.918. Within sunny:
humid: high(2+,0) normal(0,1−) → rem 0 → G = 0.918 ← max (wind ties) air, sky: G = 0 (all warm/sunny)
“Full tree” = repeat the root procedure inside every impure branch. The efficient exam order: (1) root gains, (2) close the pure branches immediately (cloudy, rainy are all-negative — no calculation needed, say “pure → leaf”), (3) recurse only into sunny with its 3 rows. Attributes with one value in the subset (air = all warm) have gain 0 — one clause, no arithmetic. Again a tie (humid/wind) — state it. Verify at the end: run each training row down the tree; all 6 must classify correctly.
1. H(S) 2. per attr: split counts → weighted rem → gain 3. max gain = node, pure branch = leaf 4. recurse per branch on remaining attrsMaps, hashing and Bloom filters
🔴 Exam-core A map binds keys to values. Hash tables aim for expected Θ(1) lookup; Bloom filters answer approximate membership with no false negatives (under normal operation) and possible false positives.
Collision handling
Separate chaining: each bucket stores a collection. Expected operations are Θ(1+α), where load factor α=m/n. Worst case Θ(m).
Linear probing: on collision try h(k),h(k)+1,… mod n. It is cache-friendly but clusters; deletion needs tombstones, not an ordinary empty slot.
Comparison
Balanced BST lookup Θ(log m) worst-case and ordered iteration. Binary search is Θ(log m) but needs sorted random-access data. Hash table is expected Θ(1), worst Θ(m). Bloom lookup uses k bit probes: Θ(k), treated as Θ(1) for fixed k.
Bloom probability model
ε = [1−(1−1/n)^(km)]^k ≈ (1−e^(−km/n))^k
k* = (n/m)ln2
n required ≈ −m ln ε /(ln2)²
Symbols: n bits, m inserted elements, k independent uniform hash functions, ε false-positive probability. These are counts/probabilities—no units. The approximation assumes large n and ideal hashing.
Order: if choosing k, calculate real k*, test floor and ceiling with the requested exact/approximate formula, then state the better integer. More hashes are not always better: they also fill more bits.
Latest exam Problem 2(b–e) — complete
(b) A Bloom filter is a bit array plus k hash functions for space-efficient approximate membership. Insert sets all hashed bits; query checks them. A zero gives a definite “not present”; all ones give “possibly present”. Standard Bloom filters have false positives, not false negatives, and do not support ordinary deletion.
(c) Insert/query take Θ(k), or Θ(1) for fixed k; compare balanced BST Θ(log m) worst-case and hash table expected Θ(1) but with exact membership and stored keys.
(d) Statements: independent of m — false; decreases as n increases — true; false result guarantees absence — true; k=(n/m)ln2 — true as the continuous optimum (then choose an integer).
(e) n=24,m=5:
ε₃=(1−e^(−15/24))³=0.100375
ε₄=(1−e^(−20/24))⁴=0.102195
Choose k=3; approximate false-positive rate 10.04%. If the exact finite-n model is requested, substitute (1−1/24)^15 instead of e^(−15/24) and label it exact.
The optimum near 3.33 makes 3 plausibly better than 4; both probabilities are in [0,1].
Tutorial Bloom exercise: exact versus approximation
For the source’s n=10 bits, m=3 inserted elements, k=3:
approx ε≈(1−e^(−0.9))³=0.208982
The approximation reported in the source is about 20.9%; the exact idealized finite-array expression is about 23.0%. The difference is visible because n=10 is small. Use the formula the question specifies; never silently call the approximation exact.
⚔ From the real exams — drill it
Hash-table drawing and the UnsortedTableMap method are old reliables; the Bloom-filter chain (concept → multiple choice → derivation → numeric instance) is the newest recurring block — it filled half of a 25-point problem in each of the two most recent exams.
def __setitem__(self, k, v):
for item in self._table:
if k == item._key: # key exists → update
item._value = v
return
self._table.append(self._Item(k, v)) # new key → addobj[k] = v syntax → the dunder __setitem__; that name is half the marks. The other half: handle both verbs in “add or update” — scan for the key first, only append when absent. O(n) per operation is fine here; that's the point of the unsorted table (the hash table in (b) is the fix).
obj[k]=v → __setitem__ · obj[k] → __getitem__ · len() → __len__ · for..in → __iter__h(i) = (3i+2) mod 11:
13→8 41→4 19→4 88→2 23→5 94→9 11→2 39→9 5→6
bucket: 0 1 2 3 4 5 6 7 8 9 10
— — 88,11 — 41,19 23 5 — 13 94,39 —
(c) d = {}; d[k1] = {}; d[k1][k2] = v — or from collections import defaultdict; d = defaultdict(dict).
Pure calculator work: 3i+2, then the mod-11 remainder — write the h(i) line per key (that's the “supported” part), then the bucket table with chains in insertion order. Collisions here: buckets 2, 4, 9 each chain two keys. Every value machine-verified. The nested-dict two-pointer: plain dict needs the inner dict created first; defaultdict(dict) does it automatically.
chaining: list per bucket · load α = m/n · expected O(1+α) · defaultdict(dict) for nesting(e) Bit array of n bits + k hash functions. Insert x: set bits h₁(x)…h_k(x). Query x: all k bits 1 → “probably in S”; any bit 0 → “definitely not in S”. False positives possible, false negatives impossible. Time O(k) = O(1) per op; memory n bits ≪ storing S → keep the filter in RAM as a prefilter for the 10⁹ usernames, hit disk only on (rare) positives. Faster than binary/BST O(log n), comparable to hashing but far smaller.
(f) True: 2 (more bits → lower FP rate), 3 (a 0-bit proves absence), 4 (k* = (n/m)·ln 2). False: 1 — FP probability grows with m.
The (e) description has a fixed skeleton the examiner looks for: structure (bits + k hashes), the two operations, the asymmetry (FP yes / FN no), the complexity, and the application sentence tying it to the story. The MC options are recycled across years with the same four claims — memorize which is the false one: FP depends on m (the fuller the filter, the more false positives).
Bloom: n bits, k hashes · ins: set k bits · query: all 1 ⇒ maybe, any 0 ⇒ NO · FP yes/FN no · O(k)Derivation: (1−1/n)n·k/n ≈ e−k/n, because (1−1/n)n → 1/e for large n.
Numeric (m=5, n=24): k* = (n/m)·ln 2 = (24/5)·0.693 = 3.33 → k = 3 (integer).
ε = (1 − e−km/n)k = (1 − e−15/24)³ = (1 − 0.535)³ = 0.465³
The guided derivation only wants the limit identity (1−1/n)n ≈ e−1 applied to the exponent the question already arranged for you. The numeric instance is the full pipeline: k* formula → round to integer (the statement demands it — 3.33 → 3; checking k=3 vs k=4 both is the safe move, k=3 gives ε≈10.0%, k=4 ≈10.1%, so 3 wins) → plug into the FP formula with your integer k, not 3.33. All numbers machine-verified. Keep 3+ digits through the calculation, round at the end.
P(bit=0)=(1−1/n)^km ≈ e^−km/n · ε=(1−e^−km/n)^k · k*=(n/m)ln2 (round, test neighbors!) · (1−1/n)^n≈1/eAkra–Bazzi, Strassen and selected topics
🔴 Exam-core The selected-topics tutorial explicitly limits exam relevance to Bloom filters, selection and median-of-medians, but the latest exam also asks Strassen. Learn its algebra and recurrence.
Akra–Bazzi for unequal subproblems
find p from Σᵢ aᵢbᵢ^p=1
T(x)=Θ(x^p(1+∫₁ˣ g(u)/u^(p+1) du))
Assumptions: aᵢ>0, 0<bᵢ<1, regularity conditions, and lower-order perturbations do not dominate. Cue: recursive fractions differ, so Master theorem cannot apply.
Worked: for median-of-medians, T(n)=T(n/5)+T(7n/10)+Θ(n). Solve (1/5)^p+(7/10)^p=1 numerically, p≈0.8398. With g(u)=u, the integral is ∫u^(−p)du=Θ(n^(1−p)); multiplied by n^p gives Θ(n).
If you report only Θ(n^p), you forgot the combine-work integral.
Latest exam Problem 3(c–g) — matrix multiplication and Strassen
(c) Naive: each of n² output entries is a dot product of length n, so Θ(n³) scalar operations.
(d) Block product:
C₂₁=A₂₁B₁₁+A₂₂B₂₁ C₂₂=A₂₁B₁₂+A₂₂B₂₂
(e) Verify C₂₁: from the definitions M₂=(A₂₁+A₂₂)B₁₁ and M₄=A₂₂(B₂₁−B₁₁):
=A₂₁B₁₁+A₂₂B₂₁=C₂₁
(f) Seven half-size multiplications plus matrix additions: T(n)=7T(n/2)+Θ(n²).
(g) Master theorem: a=7,b=2,c=log₂7≈2.807355. Since n²=O(n^(c−ε)) for e.g. ε≈0.8, Case 1 gives T(n)=Θ(n^(log₂7))≈Θ(n^2.807).
It must beat cubic asymptotically because 2.807<3, but constants mean it need not win for tiny matrices.
⚪ Low priority Random-number generation, fast inverse square root, prime-number background, Coppersmith–Winograd and AlphaTensor provide context in the slides but were not selected as exam-relevant by the SS26 selected-topics tutorial. Know only their purpose if time remains.
⚔ From the real exams — drill it
The Strassen chain — naive complexity → block partition → correctness check → recurrence → Master theorem — has appeared as a near-verbatim 18-point run in three of the last four exams. It is the most predictable derivation in the course.
(c) O(n³) — C has n² entries; each is a dot product of a row and a column: n multiplications. n²·n = n³.
(d) Standard block multiplication:
C11 = A11·B11 + A12·B21 C12 = A11·B12 + A12·B22 C21 = A21·B11 + A22·B21 C22 = A21·B12 + A22·B22
(c) is bound + justification in one line — the n² × n sentence is the “supported” part. (d): block matrices multiply exactly like 2×2 numbers, just with matrix blocks — row-times-column at block level. 8 block multiplications of half size; that's the setup Strassen improves on (8 → 7).
naive matmul O(n³) (n² entries × n each) · blocks: Cij = Ai1·B1j + Ai2·B2jC21 = M2 + M4
= (A21+A22)·B11 + A22·(B21−B11)
= A21·B11 + A22·B11 + A22·B21 − A22·B11
= A21·B11 + A22·B21 ✓ (matches block formula)Substitute the given M-definitions, expand (distributivity holds for matrices), cancel the ±A22·B11 pair, and land on the block formula from (d) — then say that's what it had to equal. Four lines, no cleverness. Note: the M₁…M₇ definitions and the Cᵢⱼ recombination are always printed in the exam — never memorize them; only the expansion technique and the target (the (d) formulas) matter. The exam has asked C1,1 and C2,1 in different years — same three moves.
Strassen check: substitute given M's → expand → cancel → block formula (M's are provided, don't copy them)(f) T(n) = 7·T(n/2) + O(n²) — 7 half-size multiplications + matrix additions.
(g) Master theorem, T(n) = a·T(n/b) + O(nd): a=7, b=2, d=2. Applicable: a≥1, b>1, f polynomial.
Compare d vs log_b a: log₂7 ≈ 2.807 > 2 = d → recursion-dominated case.
The cover-sheet rule bites precisely here: “if you use a fundamental theorem you must indicate this and explain why it applies.” So the full-marks skeleton is: name (Master theorem) → parameters (a=7 subproblems, b=2 size divisor, d=2 from the additions) → applicability → case comparison (log₂7 > d) → result → interpretation (asymptotically faster than n³). Same chain answers merge sort (a=2,b=2,d=1 → n log n) and binary search (a=1,b=2,d=0 → log n) — one memorized procedure, three recurring exams questions.
T=aT(n/b)+O(n^d): d>log_b a → n^d · d=log_b a → n^d log n · d<log_b a → n^log_b a + instances Strassen 7/2/2→n^2.807 · merge 2/2/1→n log n · binary 1/2/0→log nQuantum computing: broad, precise fundamentals
🔴 Exam-core Revision says quantum appeared in the last three exams with different questions. Prepare qubits, measurement, gates, reversibility, decoherence, Grover and Shor—not one memorized paragraph.
Valid qubit and measurement
α,β ∈ ℂ, |α|²+|β|²=1
P(0)=|α|², P(1)=|β|²
Meaning: amplitudes α,β are complex numbers, not probabilities; squared magnitudes are probabilities. A classical bit has a definite 0 or 1. A qubit may be in a coherent superposition until measured; measurement returns a classical outcome and changes the state.
Validity check: compute complex magnitude |a+bi|²=a²+b²; totals must equal 1 (within rounding).
Worked qubit
Let α=1/√3 and β=i√(2/3). Then |α|²=1/3, |β|²=2/3; total 1, so valid. Measurement gives 0 with probability 1/3, 1 with 2/3.
Hadamard and X
H=(1/√2)[[1,1],[1,−1]]
X|0⟩=|1⟩. H|0⟩=(|0⟩+|1⟩)/√2. Quantum gates are unitary, hence reversible; measurement is not a unitary reversible gate.
Grover
Find a marked item in an unstructured space of size N using about Θ(√N) oracle calls versus classical Θ(N). It is a quadratic, not exponential, speedup. Assumes oracle access and high-probability success.
Shor
Factors integer N by reducing factoring to period finding, then using the quantum Fourier transform. Course slides give polynomial time roughly O((log N)²(log log N)(log log log N)) for the quantum arithmetic model, versus the best known classical general number-field sieve, sub-exponential in N. State the cost model; do not say classical factoring is simply O(2ⁿ) without defining n.
Latest exam Problem 3(a–b) — exam-ready answer
(a) α,β are complex probability amplitudes. A valid qubit satisfies |α|²+|β|²=1; measurement yields 0/1 with those probabilities. Unlike a classical bit, the pre-measurement state can be a coherent superposition and can exhibit interference.
(b) Shor’s algorithm is a quantum integer-factoring algorithm using modular exponentiation, period finding and the QFT. It runs in polynomial time in the input bit-length under the course cost model, whereas the best known general classical factoring algorithm is sub-exponential but super-polynomial; therefore it provides a super-polynomial speedup and threatens RSA for sufficiently capable fault-tolerant quantum computers.
Reversibility
Unitary gates preserve information and inner products, so every gate has inverse U†. Classical AND is not reversible without extra bits because several inputs share an output.
Decoherence
Uncontrolled environment interaction destroys usable phase relationships, turning coherent superposition into classical-looking mixtures and causing errors. Isolation, fast control and error correction combat it.
Adiabatic vs gate model
Gate model applies discrete unitary gates. Adiabatic computing evolves a Hamiltonian slowly from an easy ground state to one encoding the solution. D-Wave hardware context is ⚪ Low priority.
⚔ From the real exams — drill it
The qubit-definition question is word-for-word identical in the last three exams; Grover and Shor alternate. This is the cheapest 7 points in the exam if the three sentences are ready.
α, β are complex probability amplitudes of the basis states |0⟩ and |1⟩. A bit is 0 or 1; a qubit is a superposition of both until measured — measurement yields 0 with probability |α|², 1 with |β|².
Constraint: |α|² + |β|² = 1 (α, β ∈ ℂ) — probabilities must sum to 1.
Three graded atoms: what α/β are (amplitudes, with the |·|²-gives-probability link), the bit-vs-qubit difference (superposition + collapse on measurement), and the normalization constraint. Your old handwritten sheet had exactly |α|²+|β|²=1 — kept. If gates appear (older exams): X = [[0,1],[1,0]] flips amplitudes (NOT); Hadamard H = (1/√2)[[1,1],[1,−1]] maps |0⟩ to (|0⟩+|1⟩)/√2 — an equal superposition.
|a⟩=α|0⟩+β|1⟩ · α,β∈ℂ amplitudes · P(0)=|α|², P(1)=|β|² · |α|²+|β|²=1 · X=[0 1;1 0] · H=1/√2[1 1;1 −1]Grover: quantum search over n unstructured items using amplitude amplification — repeated oracle + reflection steps boost the marked item's amplitude. Finds it in O(√n) oracle queries vs classical O(n) — a quadratic speedup.
Shor: quantum integer factorization via the quantum Fourier transform (period finding). Runs in polynomial time, ≈ O((log N)³), vs super-polynomial (sub-exponential) best known classical — an exponential speedup; breaks RSA-style cryptography.
Both follow the same 3-sentence template the question dictates: what problem + one mechanism phrase + speedup in O notation with the classical comparison. The classification pair to keep straight: Grover = quadratic (search), Shor = exponential (factoring). In the 2025 Summer context question (“how could a quantum computer do the lookup?”) the answer is Grover applied to the unsorted username set: O(√n) instead of O(n).
Grover: unstructured search O(√n) vs O(n), quadratic · Shor: factoring poly O((log N)³) vs super-poly, exponential, breaks RSATransfer mock: three new combinations
🔴 Exam-core Set 90 minutes, choose two problems, and write complete working. These are generated from recurring source families but use new combinations to resist pattern memorization.
Problem 1 · Code, graphs and complexity (25)
- (8) Write a generator that yields a singly linked list in reverse order without changing it. State time and auxiliary space.
- (8) Run Dijkstra from S on edges S–A:2, S–B:5, A–B:1, A–C:6, B–C:2. Give settle order, distance table and path S→C.
- (9) Solve T(n)=3T(n/2)+n log n and justify the theorem case.
Full solution · Problem 1
1
def reverse_values(self):
stack = []
current = self.head
while current is not None:
stack.append(current.element)
current = current.next_element
while stack:
yield stack.pop()Each node is visited/pushed/popped once: Θ(n) time, Θ(n) auxiliary space. A singly linked list has no backward pointers, so preserving the list while reversing output requires stored history (or recursion with the same asymptotic stack).
2
Start (S=0,A=∞,B=∞,C=∞). Settle S → A=2,B=5. Settle A → B=min(5,3)=3,C=8. Settle B → C=min(8,5)=5. Settle C. Order S,A,B,C; path S–A–B–C, cost 5.
3
a=3,b=2,c=log₂3≈1.585. f(n)=n log n=O(n^(c−ε)), e.g. any small ε<0.585 eventually dominates the logarithm. Master Case 1: Θ(n^(log₂3)).
Problem 2 · ID3, Bloom and probing (25)
- (12) For rows (A,x,+), (A,y,+), (B,x,−), (B,y,+), compute gains of attributes first and second; draw the ID3 tree with an explicit tie policy.
- (7) A Bloom filter has n=80,m=10. Choose integer k and approximate ε.
- (6) Insert keys 18, 29, 40, 22 into an 11-slot table using h(k)=k mod 11 and linear probing. Show slots.
Full solution · Problem 2
1
Parent labels 3+,1−: H=−¾log₂¾−¼log₂¼=0.8113. First attribute: A branch (2+,0−), B branch (1+,1−); remainder ½·0+½·1=0.5, gain 0.3113. Second attribute: x branch (1+,1−), y branch (2+,0−), same remainder/gain. Tie: choose first by column order. A→+, B then split on second: x→−, y→+.
2
k*=8ln2=5.545. Test 5 and 6: ε₅=(1−e^(−50/80))⁵≈0.02168; ε₆=(1−e^(−60/80))⁶≈0.02158. Choose k=6, about 2.16%.
3
18→7; 29→7 occupied, so 8; 40→7 occupied, 8 occupied, so 9; 22→0. Final: slot 0:22, 7:18, 8:29, 9:40.
Problem 3 · Selection, Strassen and quantum (25)
- (9) Explain why the median-of-medians pivot discards a constant fraction and solve its recurrence.
- (8) Expand M₃+M₅ to verify Strassen’s C₁₂; derive complexity.
- (8) Determine whether [1/2, (√3/2)i]ᵀ is a valid qubit, give measurement probabilities, then contrast Grover and Shor speedups.
Full solution · Problem 3
1
At least half the group medians are ≥ the median-of-medians, and each such full group contributes at least three elements ≥ it; similarly below it. Ignoring a constant number of incomplete/pivot groups, each side discards about 3n/10, so the larger recursive side is at most 7n/10+O(1). Recurrence T(n)≤T(n/5)+T(7n/10)+Θ(n)=Θ(n) by Akra–Bazzi as shown earlier.
2
M₃=A₁₁(B₁₂−B₂₂), M₅=(A₁₁+A₁₂)B₂₂. Sum = A₁₁B₁₂−A₁₁B₂₂+A₁₁B₂₂+A₁₂B₂₂=C₁₂. Recurrence 7T(n/2)+Θ(n²) gives Θ(n^log₂7).
3
|1/2|²+|(√3/2)i|²=1/4+3/4=1: valid. P(0)=1/4,P(1)=3/4. Grover gives quadratic query speedup Θ(N)→Θ(√N) for unstructured search. Shor gives polynomial-time quantum factoring in bit-length versus best known classical super-polynomial/sub-exponential factoring: a super-polynomial speedup.
Self-mark rule
Give yourself no calculation marks for a naked final number. A full-mark line contains the formula/theorem, mapped symbols, applicability, substitution, result, and one check or interpretation. For code, run the empty, one-element, ordinary, and boundary cases mentally.
One-side A4 synthesis
This is deliberately compressed and should be copied by hand only after you can explain each line. Use the button to print just this page as a drafting template.
Asymptotics
Order: 1 ≺ log n ≺ n ≺ n log n ≺ n² ≺ n³ ≺ cⁿ ≺ n!. O upper, Ω lower, Θ tight. Recursive stack space Θ(depth).
Master
T=aT(n/b)+f; c=log_ba
f polynomially smaller → Θ(nᶜ). f=Θ(nᶜlogᵏn) → Θ(nᶜlogᵏ⁺¹n). f larger + regularity → Θ(f).
Akra–Bazzi
Σ aᵢ bᵢᵖ = 1
T = Θ(nᵖ · (1 + ∫₁ⁿ g(u)/uᵖ⁺¹ du))
Sort
merge Θ(n log n), stable, Θ(n) space; quick expected Θ(n log n), worst n², unstable; heap Θ(n log n), in place, unstable; comparison lower bound Ω(n log n). Stable preserves equal-key order.
Selection
Quickselect avg Θ(n), worst n². MoM groups 5; pivot median of medians; recurrence T(n/5)+T(7n/10)+Θ(n)=Θ(n).
Lists/ADTs
Array index Θ(1), middle Θ(n), append amortized Θ(1). Linked access Θ(n), insert with predecessor Θ(1). Stack LIFO; queue FIFO; deque both. Pointer insert: new.next=prev.next; prev.next=new; size++.
Graphs
Adj list space V+E; matrix V². BFS unweighted shortest; Dijkstra nonnegative: settle min tentative, relax d[v]>d[u]+w. Heap O((V+E)logV).
Trees/ID3
Traversal Θ(n), stack Θ(h). Balanced BST operations Θ(log n), degenerate Θ(n).
H=−Σp log₂p
Rem(A)=Σ|Sᵥ|/|S|H(Sᵥ)
IG=H(parent)−Rem
Calculator: log₂x=ln x/ln2. Max IG; recurse impure branches; state ties.
Hash/Bloom
Hash expected Θ(1), worst Θ(m). Chaining; probing (tombstone delete).
ε≈(1−e^(−km/n))ᵏ
k*=(n/m)ln2
n≈−m lnε/(ln2)²
Any zero = definitely absent; all ones = maybe. No false negatives normally. Check floor/ceil k.
Strassen
Naive Θ(n³). 7 half products + Θ(n²): T=7T(n/2)+n²=Θ(n^log₂7)≈n^2.807. Know M₂+M₄=C₂₁ expansion.
Quantum
|ψ⟩=α|0⟩+β|1⟩; |α|²+|β|²=1
P(0)=|α|². X swaps; H creates/interferes superposition. Gates unitary/reversible; measurement not. Grover Θ(√N) vs Θ(N). Shor polynomial in bit-length vs classical sub-exp. Decoherence = environment destroys phase.
Exam script
Name theorem → map parameters → check assumptions → substitute → result → sanity check. 4 min choose; 38 min each; 10 min check. Unsupported answer = no credit.
Notation and compact glossary
n — input size; in Bloom formulas specifically number of bits. Never assume the same meaning across questions.
m — often inserted elements in Bloom filters; sometimes edges or another count. Define it.
k — rank in selection, hash-function count in Bloom, or logarithmic exponent in Master Case 2.
|V|, |E| — graph vertex and edge counts.
h — tree height or hash function, by context.
α — load factor in hashing, or complex amplitude in a qubit. Context is essential.
ε — positive comparison gap in theorem cases, or Bloom false-positive probability.
ADT — abstract data type: behavior/operations independent of implementation.
Amortized — average per operation over any worst-case sequence, not probabilistic average-case.
Auxiliary space — extra working memory, normally excluding input/output storage.
Invariant — property true before and after each iteration/mutation.
Stable sort — equal-key records keep relative input order.
Relax — improve a tentative shortest-path estimate through an edge.
Entropy — uncertainty in a categorical distribution; base-2 units are bits.
False positive — reports possibly present when absent. Bloom filters permit these.
Qubit — normalized complex two-amplitude state; measurement yields a classical bit.
Scope traced to Syllabus, p. 1, Slides, pp. 1–738, all Tutorial-SS26 sheets and solutions, Coursework, and supplied exams 2020–2025/26. Professor-slide screenshots are not reproduced; all diagrams here are original SVG.
What the professor said — use this to finish tonight
🔴 Prof-confirmed From a direct call with Prof. Glauner on 19 July. Everything below is his framing, not inference from past exams — where it refines or overrides earlier chapters, this section wins. Items marked IN he stated will appear; OUT he stated will not.
Tonight's 90 minutes, in his order of value
- ID3 full tree, 15 of 25 pts — rehearse one complete build (drill). His words: “if you do the decision tree learning right, it's 15 points… you need 20 to pass.” Bigger dataset than last time (more attributes = more points), so practice the 6-attribute 2024–25 resit instance.
- Akra–Bazzi with an easy integer p — one guaranteed problem (worked recipe below).
- Bloom filter, applied — re-do
Tutorial-SS26\8-Selected-Topics\BloomFilterExercises.pdf; he said the exam task is “quite similar.” - Symmetric trees — write
is_symmetric+mirroronce from memory (drill). - Hash table by chaining, graph definition + adjacency matrix, Deggendorf-style recursion — one pass each (hash · graphs · recursion).
- Quantum BogoSort — read the one paragraph below; that is the whole quantum scope.
Confirmed topic by topic
IN Akra–Bazzi — one dedicated problem
“There will be one problem about Akra–Bazzi… you need to find the p, and the p will be a very small integer.” Expected flow: a recurrence is given → first argue whether the Master theorem applies (it won't — unequal subproblem sizes) → then solve with Akra–Bazzi. Full recipe in the box below.
IN ID3 — full tree, 15 points
Complete decision-tree build with intermediate steps on a fresh dataset. Budget ~25 minutes. Pure branches: write “pure → leaf,” no arithmetic. State ties. Your drilled procedure is exactly what's needed.
IN Bloom filter — “super important,” applied
Given hash values for keys: set bits in the array, then query — any 0-bit ⇒ “definitely not in S”; all 1 ⇒ “probably in S.” Know the theory too, but he “will not quiz” the ε-derivation this time. Crucial: complexity is O(k) — depends on the number of hash functions, not the number of elements — and compare it to the other lookup structures.
IN Hash tables — apply, don't implement
“You're given a hash function and some keys, insert them into a hash table and think about the collisions… we mainly worked with chaining.” Exactly the 2024 Summer P2(b) drill with new keys. No hash-function design, no code.
IN Graphs — formal definition + adjacency matrix
“Just a different graph.” Same two moves as the 2025 Summer drill: k-tuple G=(V,E,w) with explicit sets, then the matrix with a declared row-convention.
IN Symmetric trees — determine + implement recursively
“Very important for tomorrow… a tree is symmetric if it's equal to its inverse. Sometimes I copy-paste stuff from past exams.” Structure only, values ignored — the whole is_symmetric incl. outer function, as in the 2024–25 resit drill.
IN Recursion — Deggendorf-style + lab problems
“For recursive functions… these Deggendorf numbers from the past exams, something like this,” plus the lab-problems video he shared. Keep the naive → complexity → memo → lru_cache chain hot (drill).
IN Quantum — small, combined with sorting
One playful task: quantum BogoSort (below). “That's all about quantum computing this year.” Keep the qubit definition ready anyway — it costs one line on your sheet and has appeared verbatim three exams running.
OUT Dropped this year — stop drilling these tonight
- Strassen: “not part of this year's exam” (selected topics = only what was discussed: Bloom, selection, median-of-medians).
- Median-of-medians: “nothing specific in the exam,” no code — it served to motivate Akra–Bazzi.
- Bloom ε-derivation quiz: “I will not quiz you on this stuff” — the applied run-through replaces it.
- Hash-table implementation code: apply a given hash function only.
No comment given — stay lightly ready
Lists/arrays/ADTs: “I'm not commenting on that.” Keep __iter__, remove_first, and the MyArray patterns on your sheet — they're cheap insurance and recur historically. Format unchanged: 90 min, 2 of 3 × 25 pts, 20 to pass, one handwritten A4 side + non-CAS calculator.
The Akra–Bazzi recipe he described — finding p without pain
When Master fails: Master needs every subproblem to have the same size divisor (one b). A recurrence like T(n) = T(n/5) + T(7n/10) + O(n) splits unevenly → say exactly that, then switch to Akra–Bazzi:
Finding p (his exact guidance: “a very small integer — try p = 1, 2, 3”): the three tests cost seconds each:
- p = 0 works ⇔ Σ aᵢ = 1
- p = 1 works ⇔ Σ aᵢ/bᵢ = 1 (sum of the fractions of n equals 1 — e.g. T(n/2)+T(n/3)+T(n/6): ½+⅓+⅙ = 1 ✓)
- p = 2 works ⇔ Σ aᵢ/bᵢ² = 1 (e.g. 3T(n/2)+4T(n/4): ¾ + 4/16 = 1 ✓)
- None hits? Bracket it on the calculator like binary search on p (he confirmed 0.8398 for median-of-medians this way) — “but that would not happen in the exam.”
Then skip the integral — for g(n) = Θ(n^d) the result collapses to a Master-like trichotomy (this is what the integral evaluates to):
Write in this order: (1) “Master theorem not applicable — unequal split sizes.” (2) The Σ aᵢ(1/bᵢ)ᵖ = 1 line with your integer test shown. (3) The trichotomy comparison d vs p. (4) Boxed Θ. That is the “name the theorem + why it applies” structure the cover sheet demands.
Quantum BogoSort — the whole quantum question
Classical BogoSort: shuffle the list; check if sorted (O(n)); repeat until sorted by chance. Best case O(n) (already sorted), expected O(n·n!) (n! orderings, one is sorted), worst case unbounded — a deliberately absurd algorithm.
Quantum BogoSort (the joke he described): use quantum randomness to put the list into a superposition of all n! permutations simultaneously; observe/measure. In the branch where the measurement finds it sorted, you're done after one shuffle — O(n) total, the cost of the sortedness check. (The punchline version: if it's not sorted, destroy the universe — in every surviving universe the list is sorted.) The serious sentence to close with: it's not a real algorithm — measurement collapses to one random permutation, so this is a parody of “quantum = try everything at once,” not a usable speedup; contrast Grover, which gives a genuine but only quadratic O(√n) advantage.
The call itself — 19 July, transcript
🟡 Context The raw material behind the briefing above. Reconstructed from an automatic transcription of the call: speaker attribution inferred, wording lightly cleaned where the ASR garbled it, small talk kept as asides. Where the briefing paraphrases, this is what was actually said.
▸ Open the full formatted transcript — Akra–Bazzi · exam format · hashing & Bloom · ID3 · symmetric trees · quantum · logistics
Opening · Akra–Bazzi and finding p
PROF You have done well in so many advanced courses, so we want you to succeed with this thing tomorrow.
ALEX I have a couple of questions on topics I don't fully understand. I understand the formulas and how to apply them — for example Akra–Bazzi — but I don't quite understand how to find the p, or how you choose a p. It's shown in the slides, I think. Maybe I can share my screen and you show me where the p comes from. The tutorial slides just… already give a p.
[screen share; searching the slides for “Akra” — nothing found at first; it's near the end of Selected Topics]
ALEX Your slide count has grown — I remember ~300 slides, now it's ~700.
PROF It's a lot of selected material, and we don't do all the selected topics every year. The other chapters grew a little… we're at the Bloom filter here, and just after it comes selection.
ALEX Selection was just a 20-minute video, so I assumed it won't really appear — or only very simply.
PROF Selection is the basis; then we saw how to speed it up, and for that we looked at the median of medians. And Akra–Bazzi is here.
ALEX As I understood it, Akra–Bazzi is just an alternate way of finding algorithm complexity?
PROF Not the ultimate way — it's a generalization of the Master theorem for this type of recurrence. And that depends on your recurrence: you have your a's and b's, and then the sum — a·(1/b)ᵖ terms — like (1/5)ᵖ + (7/10)ᵖ, all equal to 1. We have one example there.
ALEX But the slides just say “approximately” — that's what I mean. How do we find the p on the calculator?
PROF Here you often don't even need a calculator. You write down the formula and try a relatively simple p — maybe 1, 2, 3 — and you find the solution quickly. If it's not a simple integer you'd need an approximation technique like Newton's method — but that's what I would not put in the exam. With a calculator you can also bracket it: try 0, then 1, then 0.5, and keep halving the interval, like binary search — that's how you'd get the 0.839 in the median-of-medians example. But that would not really happen in the exam. If you need to determine the p, it will be a relatively easy-to-find integer.
Exam format · what's in, what's out
ALEX My biggest concern: will it be like the summer 2025 exam we reviewed — applied code, guided calculations — or like the winter 24/25 exam, which was heavy on derivations — recurrences, Strassen, full ID3, everything mixed and very theory-heavy?
PROF The Strassen algorithm is not part of this year's exam — we only ask about the selected topics that were actually discussed: Bloom filter, selection, and median of medians. And median of medians includes the Akra–Bazzi method — we introduced it there because our other tools couldn't determine that recurrence's complexity.
ALEX Will there be a dedicated selected-topics problem, or will it be spread across all three like in 2020?
PROF That can happen. What I can tell you for sure — and I emphasized it in the discussion too — there will be one problem about Akra–Bazzi. You need to find the p, and the p will be a very small integer. You write down that formula we had and find it very easily.
ALEX The p was my biggest problem with Akra–Bazzi. Where will it appear — in which form?
PROF You'll be given a recurrence relation and need to determine its complexity. First think about whether it can be solved with the Master theorem — you need to argue. Maybe you notice it can't. Then you may be asked to solve it with the Akra–Bazzi method.
ALEX And median of medians — I see it as mostly math and theory. I don't remember us implementing it.
PROF Oh, we implemented it — we introduced it this year and implemented it. But no, you won't be asked to write code on median of medians. And — I'll tell you this, but don't tell the others — there will be nothing specific in the exam about the median of medians. What is there is the Akra–Bazzi part, about some other recurrence relation.
Code depth · hashing & Bloom
ALEX When we're asked to implement basic functions — __iter__, remove_first, merge, the MyArray methods — I'm fine. But hash tables… I've watched that lecture three times. I get the concept of hashing and collisions, but the implementation…
PROF You don't need to implement it. There will be something about hash tables and collisions, but rather an example: you're given a hash code or a hash function, and some keys, and you insert them into a hash table and think about the collisions. There were such examples in past exams, and we have something like this this year as well.
[aside — ordering an alcohol-free Radler; birthday hotel stay; brief agreement that beer is basically water]
ALEX You said there will be new questions — how new will the exercises be?
PROF There will be problems you've seen in past exams, maybe with a new dataset, a new graph, or new keys. And some new stuff, obviously. But if you did the coursework, watched the lectures, and did the past exams, there isn't too much new. There will be something about graphs — a graph, you need the formal definition and the adjacency matrix. Just a different graph. Knowing that will be very helpful for tomorrow.
PROF We mainly worked with chaining for collisions — that's important for tomorrow as well. And hashing also connects to the Bloom filter: like in Meltem's tutorial, some strings or values need to be added into a Bloom filter, and for that you need hashing too.
ALEX With hashes, my problem was the variety — every kind of collision handling…
PROF No — this time you apply hashing and collision handling by chaining, just with an example. You don't need to write code. We also didn't discuss what a good hash function would be — you're given hash functions and apply them.
ALEX If that's the case, I assume lists, arrays, and ADTs won't appear?
PROF I'm not commenting on that. Everything we discussed in class is important, but I've highlighted a few things. Something super important this year is the Bloom filter. In the tutorial we practiced running values into a Bloom filter and determining if something is not in it or most likely in it. In past exams I asked theory or equations about it — this year you need to put it to life. Not coding — you calculate through the examples. If you did that in the tutorial, it will be quite similar. But you still need the theory. And what's crucial — we discussed it last week — the complexity of the Bloom filter does not depend on the number of elements, but on the number of hash functions: O(k), and how it compares to other algorithms and data structures for efficient lookup.
ALEX Would we also need to derive ε — the e-approximation and so on?
PROF This time I will not quiz you on this stuff. But you need to know the O(k) and what it means.
ID3 · symmetric trees
ALEX ID3 appeared last exam, so I assume this time it won't — the full calculation?
PROF Oh, it will appear with a full calculation this time. There will be a dataset — 15 points to build the whole decision tree with ID3. If you do that right, you have almost passed: you need 20 of 50 points. Last time it was 11 points because it was a smaller dataset with fewer attributes — more attributes, more points. It's 50 points for 90 minutes, so 15 points is around 25 minutes — doable with practice.
PROF And when it comes to trees — in a past exam you had to determine if trees are symmetric. That's something… knowing this for tomorrow, and how to implement it, would be very helpful. A tree is symmetric if it's equal to its inverse. You determine it for the whole tree, recursively. Sometimes I copy-paste stuff from past exams. The problems only looked at the structure, not the values — assume it's the same this time.
ALEX Comparing left and right — outer with outer, inner with inner, recursively. I know that function; I've implemented it in my head so many times.
Quantum · BogoSort
ALEX You mentioned quantum — will there be much?
PROF Something small — there will be a problem combining quantum computing and sorting. Do you remember BogoSort? You shuffle the dataset; if it's sorted by chance you're done, otherwise shuffle again. Obviously nonsense. Now think about what happens if you combine BogoSort with quantum computing. It's made up, not practical — more like a joke, a quantum BogoSort. That's all about quantum computing this year.
Closing · logistics & his final checklist
[aside — Math 2 retake went much better this time; SVD eigenspaces were the one gap; the professor: “make sure to get enough rest for tomorrow”]
PROF You know which room tomorrow? The exam is in several rooms — I'm proctoring in a different one, in the C building.
[Alex: reassigned on campus; room confirmed via email. Also: interest in the quantum computing course after results — “you'll hear from me once I have marked the exams.”]
PROF For tomorrow, remember which stuff is super important: graph definitions, hash tables — there are past examples of putting values in a hash table, drawing it, handling collisions by chaining. Then the trees: symmetric trees, decision trees. And for recursive functions — these Deggendorf numbers from past exams, and the lab problems video I shared. Something like this will also be very important for this year's exam.
ALEX Thank you so much.
PROF I wish you all the best, and I hope this session helped. And the stuff I told you — don't share it.