Minimum Difficulty of a Job Schedule is a classic interval-partition DP. You must split an ordered list of jobs into exactly d days, and the goal is to make the sum of each day's hardest job as small as possible.
Problem. You are given jobs, where jobs[i] is the difficulty of the i-th job, and an integer d.
Jobs must be done in order: to start a job you must finish every earlier one. You schedule the jobs
over exactly d days, doing at least one job per day. A day's difficulty is the maximum difficulty
among the jobs done that day, and the schedule's difficulty is the sum of the daily difficulties.
Return the minimum possible schedule difficulty, or -1 if it is impossible.
Example: jobs = [6, 5, 4, 3, 2, 1], d = 2 → answer 7 (day 1 = [6,5,4,3,2] costs 6, day 2 = [1] costs 1).
The slow way first
Each day eats a contiguous block of jobs (because jobs run in order), so a schedule is really a choice of where to cut. With d days you place d - 1 cuts among the jobs. Brute-forcing every placement of those cuts is exponential — far too slow once the array grows.
The question to ask: given that I have already placed some days, does my next choice depend on anything except where I currently am and how many days are left? It does not. That smells like dynamic programming.
The idea: split point + max of the last day
Think about the last day. It takes some contiguous suffix jobs[j..i], and its cost is max(jobs[j..i]). Everything before it — jobs[0..j-1] — must be packed into d - 1 days, which is the same problem one size smaller.
So define dp[d][i] = the cheapest way to schedule jobs[0..i] in d days. Then:
dp[d][i] = min over j of ( dp[d-1][j-1] + max(jobs[j..i]) )
We slide the split j, keep a running max of the last day, and add the best solution to the prefix.
The key insight: as the last day's left edge j moves left, it can only swallow more jobs, so its max only grows — we update it in O(1) while scanning.
Walk through it
Step through the animation. The split pointer marks where the last day begins. For jobs = [6,5,4,3,2,1] and d = 2, we try each cut: cutting early leaves the hard jobs sharing a day (total 11), and cutting late isolates a single easy job on the last day. Because day 1 already contains the 6, its cost is stuck at 6 no matter what — so the win comes from making day 2 as cheap as possible: just [1], for a total of 7.
Pseudocode
n = number of jobs
if n < d: return -1 # cannot give every day a job
define solve(i, days): # min cost for jobs[0..i] in "days" days
run_max = 0
best = infinity
for j from i down to days-1: # last day starts at j
run_max = max(run_max, jobs[j]) # last day's hardest job
if days == 1: rest = 0 # no prefix left
else: rest = solve(j-1, days-1)
best = min(best, run_max + rest)
return best
return solve(n-1, d)The Python solution
def min_difficulty(jobs, d):
n = len(jobs)
if n < d:
return -1
INF = float('inf')
# dp[k][i] = min cost to schedule jobs[:i+1] in k days
def solve(i, days):
run_max = 0
best = INF
for j in range(i, days - 2, -1):
run_max = max(run_max, jobs[j])
rest = 0 if days == 1 else solve(j - 1, days - 1)
best = min(best, run_max + rest)
return best
return solve(n - 1, d)if n < dis the impossibility guard: with fewer jobs than days, some day would be empty.solve(i, days)answers: cheapest way to dojobs[0..i]indaysdays.- The loop slides
j, the start of the last day, fromileftward; it must stop early enough to leave one job for each remaining day (days - 1). run_maxis the last day's difficulty — its hardest job — updated in O(1) as the day grows.restis the best schedule for everything before the cut, solved recursively with one fewer day.- We keep the smallest
run_max + restover all cuts. Memoizingsolveon(i, days)turns the exponential search into a polynomial DP.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (all cut placements) | O(C(n, d)) (moderate) | exponential search |
| DP over (index, days) | O(n * n * d) (moderate) | n*d states, O(n) split scan each |
O(n * d) (moderate)There are n * d distinct states, and each scans up to n split points, giving O(n² * d) time. The memo table holds one value per state, so O(n * d) space.
When this pattern shows up
When a problem says split an ordered array into exactly k contiguous groups and optimize a cost
that sums a per-group function (max, sum, average), reach for a DP indexed by (position, groups used).
Painting fences, splitting arrays for the largest-sum, and this problem are all the same partition move.
Do not forget the feasibility check. Every day needs at least one job, so the split loop must leave
days - 1 jobs for the remaining days, and n < d is impossible. Skipping either bound silently
produces wrong or out-of-range answers.
Practice
For jobs = [6, 5, 4, 3, 2, 1] and d = 2, why does putting [1] alone on day 2 beat splitting in the middle?
1. Why must each day take a contiguous block of jobs?
2. In dp[d][i] = min over j of (dp[d-1][j-1] + max(jobs[j..i])), what does the max term represent?
3. When should the function return -1?
4. What is the time complexity of the DP solution?