Algorithm Paradigms
Time limit: 45 minutes Total marks: 60 Instructions: Write clear derivations, code from memory (Python-like pseudocode accepted), and explain reasoning where prompted. Show all work.
Question 1 — Divide & Conquer recurrence + Master Theorem (10 marks)
(a) State the divide-and-conquer template in 3 phases (divide, conquer, combine) in one sentence each. (3)
(b) For merge sort the recurrence is . Using the Master Theorem, derive its asymptotic complexity, showing which case applies and why. (4)
(c) For an algorithm with , derive the complexity and state which Master Theorem case applies. (3)
Question 2 — Greedy exchange argument (12 marks)
Consider activity selection: given activities with start/finish times, select the maximum number of mutually non-overlapping activities.
(a) State the greedy choice (which activity to pick first) and give the full algorithm in pseudocode. (4)
(b) Prove the greedy choice is optimal using an exchange argument. Structure your proof: assume an optimal solution not containing the greedy choice, then exchange. (6)
(c) Give the time complexity assuming the input is unsorted, and justify. (2)
Question 3 — When greedy fails: 0/1 knapsack (8 marks)
(a) Give a concrete counter-example (specific weights, values, capacity) where the greedy "highest value-to-weight ratio first" strategy fails for 0/1 knapsack. Show the greedy answer vs the optimal answer with numbers. (5)
(b) Explain in one or two sentences the structural property fractional knapsack has that 0/1 lacks, which makes greedy work for one but not the other. (3)
Question 4 — Dynamic programming from scratch: coin change (12 marks)
Given coin denominations coins and a target amount, compute the minimum number of coins needed (or if impossible).
(a) Write the recurrence relation for including the base case. (3)
(b) Write the bottom-up (tabulation) code from memory. (5)
(c) Trace your DP table for coins = [1, 3, 4], amount = 6. Show the full table and state the answer. (4)
Question 5 — LCS + Edit Distance (10 marks)
(a) Write the recurrence for the length of the Longest Common Subsequence of strings and , including base cases. (3)
(b) Compute the LCS length of X = "AGCAT", Y = "GAC". Show enough of the DP table to justify. (4)
(c) State the edit distance (Levenshtein) recurrence and give the edit distance between "cat" and "cut". (3)
Question 6 — Bit manipulation + explain-out-loud (8 marks)
(a) Given an array where every element appears twice except one, explain the XOR trick to find the unique element and why it works. (3)
(b) Write a one-line expression using bitwise ops that isolates the lowest set bit of x, and explain why it works. (3)
(c) State Brian Kernighan's algorithm for counting set bits and its time complexity in terms of the number of set bits. (2)
Answer keyMark scheme & solutions
Question 1 (10 marks)
(a) (3, one mark each)
- Divide: split the problem into smaller subproblems (usually of size ).
- Conquer: solve each subproblem recursively (base case solved directly).
- Combine: merge subproblem solutions into the solution for the original problem.
(b) (4) Master Theorem form with , , .
- Compute . (1)
- Compare with : they are the same order → Case 2. (2)
- Result: . (1)
(c) (3) , , .
- . (1)
- grows slower than (polynomially smaller) → Case 1. (1)
- . (1)
Question 2 (12 marks)
(a) (4) Greedy choice: pick the activity with the earliest finish time among compatible activities. (1 for the rule)
sort activities by finish time
last_finish = -infinity
count = 0
for each activity (s, f) in sorted order:
if s >= last_finish:
select it; count += 1
last_finish = f
return count
(3 for correct pseudocode: sort by finish, compare start to last finish, update)
(b) (6) Exchange argument:
- Let be the greedy choice (earliest finishing activity). Let be an optimal solution. (1)
- If , done. Otherwise let be the activity in with the earliest finish time. (1)
- Since finishes no later than any other activity, . (1)
- Form . Because finishes no later than , it does not overlap any activity in that started after finished, so is still a valid (non-overlapping) set. (2)
- , so is also optimal and contains the greedy choice. By induction on the remaining subproblem (activities starting after ), greedy is optimal. (1)
(c) (2) Sorting dominates: for the sort, then single pass ⇒ total . (1 answer, 1 justification)
Question 3 (8 marks)
(a) (5) Counter-example: capacity . Items: item A , item B , item C .
- Ratios: A , B , C . (1)
- Greedy takes A first (ratio 2.0, value 12, remaining capacity 4). Neither B nor C fits (each weighs 5). Greedy total . (2)
- Optimal: take B + C: weight , value . (2)
- Greedy 12 < optimal 18 ✓. (any valid counter-example earns full marks)
(b) (3) In fractional knapsack you may take any fraction of an item, so you can always fill capacity exactly by topping up with a fraction of the next-best-ratio item — the greedy exchange never leaves wasted capacity. In 0/1 knapsack items are indivisible, so committing to a high-ratio item can waste capacity and block a better combination; there is no exchange argument that preserves optimality. (3)
Question 4 (12 marks)
(a) (3) and if no coin can complete . (2 recurrence, 1 base case)
(b) (5)
def min_coins(coins, amount):
INF = float('inf')
dp = [0] + [INF] * amount # dp[0] = 0
for a in range(1, amount + 1):
for c in coins:
if c <= a and dp[a - c] + 1 < dp[a]:
dp[a] = dp[a - c] + 1
return dp[amount] if dp[amount] != INF else -1(marks: dp init & base case 1, outer loop 1, inner loop over coins 1, correct transition 1, return/INF handling 1)
(c) (4) coins = [1,3,4], amount 6:
| a | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| dp | 0 | 1 | 2 | 1 | 1 | 2 | 2 |
- dp[1]=1 (1); dp[2]=2 (1+1); dp[3]=1 (3); dp[4]=1 (4); dp[5]=min(dp[4],dp[2],dp[1])+1 = 2 (4+1); dp[6]=min(dp[5],dp[3],dp[2])+1 = min(2,1,2)+1 = 2 (3+3). (3 for table)
- Answer: 2 coins (3+3 or 4+1+1... but 3+3=2 is minimum). (1)
Question 5 (10 marks)
(a) (3) (1 base, 1 match, 1 mismatch)
(b) (4) X="AGCAT" (rows), Y="GAC" (cols). DP table (rows 0..5 for X prefix, cols 0..3 for Y prefix):
| "" | G | A | C | |
|---|---|---|---|---|
| "" | 0 | 0 | 0 | 0 |
| A | 0 | 0 | 1 | 1 |
| G | 0 | 1 | 1 | 1 |
| C | 0 | 1 | 1 | 2 |
| A | 0 | 1 | 2 | 2 |
| T | 0 | 1 | 2 | 2 |
LCS length = 2 (e.g. "AC" or "GA"). (3 table, 1 answer)
(c) (3)
(2) For "cat" vs "cut": only middle char differs (a→u), one substitution ⇒ edit distance = 1. (1)
Question 6 (8 marks)
(a) (3) XOR all elements together. XOR is commutative/associative, , and . Each duplicated value cancels to 0, leaving only the unique element. (2 explanation, 1 property)
(b) (3) Lowest set bit: x & (-x) (equivalently x & (~x + 1)). (1) In two's complement , which flips all bits above the lowest set bit and matches only at the lowest set bit position, so the AND isolates exactly that one bit. (2)
(c) (2) Brian Kernighan: repeatedly do x = x & (x - 1) (clears the lowest set bit) and count iterations until x == 0. Runs in where = number of set bits. (1 algorithm, 1 complexity)
[
{"claim":"Merge sort recurrence T(n)=2T(n/2)+n gives Theta(n log n): log_b a = 1 equals exponent of f", "code":"a=2; b=2; import sympy; lba=sympy.log(a,b); result = (lba==1)"},
{"claim":"T(n)=4T(n/2)+n has n^{log_2 4}=n^2 dominating linear f -> Theta(n^2)", "code":"import sympy; result = (sympy.log(4,2)==2)"},
{"claim":"Greedy 0/1 knapsack counterexample: cap 10, item A(6,12), B(5,9), C(5,9); greedy=12, optimal=18", "code":"cap=10; greedy=12; optimal=9+9; result = (greedy==12 and optimal==18 and optimal>greedy)"},
{"claim":"min coins for coins [1,3,4] amount 6 is 2", "code":"coins=[1,3,4]; amount=6; INF=float('inf'); dp=[0]+[INF]*amount\nfor a in range(1,amount+1):\n for c in coins:\n if c<=a and dp[a-c]+1<dp[a]: dp[a]=dp[a-c]+1\nresult = (dp[6]==2)"},
{"claim":"LCS length of AGCAT and GAC is 2", "code":"X='AGCAT'; Y='GAC'; m=len(X); n=len(Y)\nd=[[0]*(n+1) for _ in range(m+1)]\nfor i in range(1,m+1):\n for j in range(1,n+1):\n d[i][j]=d[i-1][j-1]+1 if X[i-1]==Y[j-1] else max(d[i-1][j],d[i][j-1])\nresult = (d[m][n]==2)"},
{"claim":"edit distance cat vs cut is 1", "code":"X='cat'; Y='cut'; m=len(X); n=len(Y)\nd=[[0]*(n+1) for _ in range(m+1)]\nfor i in range(m+1): d[i][0]=i\nfor j in range(n+1): d[0][j]=j\nfor i in range(1,m+1):\n for j in range(1,n+1):\n d[i][j]=d[i-1][j-1] if X[i-1]==Y[j-1] else 1+min(d[i-1][j],d[i][j-1],d[i-1][j-1])\nresult = (d[m][n]==1)"}
]