Problem Description
Given an exam with multiple questions of varying point values and time requirements, design an efficient algorithm to maximize the total points earned within a fixed time limit.
Problem Constraints
- There are n questions in the exam
- For each question i:
- Worth points
- Takes minutes to solve
- Must spend full minutes or leave blank (no partial credit)
- Total exam duration is M minutes
- The goal is to maximize total points earned
Example
For an exam with 4 questions, the parameters might be:
With a total time limit M = 30 minutes.
Understanding the Solution
Dynamic Programming Approach
This problem is a variation of the 0/1 knapsack problem, where:
- Items are exam questions
- Values are question points
- Weights are time requirements
- Capacity is the total exam duration
Let’s break down the solution step by step.
1. State Definition
Let dp[i][t] represent the maximum points possible using the first i questions within t minutes.
2. Recurrence Relation
For each question i and time t, we have two choices:
- Skip the question
- Take the question if we have enough time
This gives us the recurrence relation:
3. Base Cases
- for all t (no questions)
- for all i (no time)
Example Walkthrough
Let’s solve a small example with M = 30 minutes and these questions:
- 10 points, 5 minutes
- 25 points, 15 minutes
- 15 points, 10 minutes
Step by step solution:
- Initialize dp table with zeros
- For question 1:
- Can be solved in 5+ minutes → 10 points
- For question 2:
- Can be solved in 15+ minutes → 25 points
- Or combine with question 1 if time permits
- For question 3:
- Consider all combinations with previous questions
- Find optimal selection within 30-minute limit
Solution Implementation
def optimal_exam_strategy(n, points, minutes, M):
dp = [[0] * (M + 1) for _ in range(n + 1)]
selected = [[False] * (M + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for t in range(M + 1):
# Don't take question i
dp[i][t] = dp[i-1][t]
# Take question i if enough time
if t >= minutes[i-1]:
include = dp[i-1][t - minutes[i-1]] + points[i-1]
if include > dp[i][t]:
dp[i][t] = include
selected[i][t] = True
# Reconstruct solution
solution = []
curr_time = M
for i in range(n, 0, -1):
if selected[i][curr_time]:
solution.append(i)
curr_time -= minutes[i-1]
return dp[n][M], solution[::-1]
Complexity Analysis
Time Complexity
- where:
- n is the number of questions
- M is the total time limit
- We fill a table of size (n+1) × (M+1)
Space Complexity
- for the dp and selected tables
- Additional for solution reconstruction
Why is this Solution Efficient?
-
Optimal Substructure
- The optimal solution contains optimal solutions to subproblems
- Once we find the maximum points for a subset of questions and time, we never need to recalculate
-
Overlapping Subproblems
- Many subproblems (different time allocations) are reused
- We store solutions in the dp array to avoid redundant calculations
-
Practical Considerations
- The algorithm handles all constraints naturally
- No partial credit is elegantly handled by the 0/1 choice
- Solution reconstruction gives the actual questions to solve