1335. Minimum Difficulty of a Job Schedule
Problem Description
You have a list of jobs that must be completed over d
days. The jobs have a dependency constraint - to work on the i-th
job, you must first complete all jobs from index 0
to i-1
. This means jobs must be done in order.
Each job has a difficulty value given in the array jobDifficulty
, where jobDifficulty[i]
represents the difficulty of the i-th
job.
The scheduling rules are:
- You must complete at least one job every day
- Jobs must be done in order (can't skip ahead)
- The difficulty of a single day equals the maximum difficulty among all jobs completed that day
- The total difficulty of the schedule equals the sum of difficulties across all
d
days
Your goal is to partition the jobs across d
days such that the total difficulty is minimized.
For example, if you have jobs with difficulties [6, 5, 4, 3, 2, 1]
and d = 2
days:
- One possible schedule: Day 1 do jobs
[6, 5, 4, 3]
(difficulty = 6), Day 2 do jobs[2, 1]
(difficulty = 2). Total = 6 + 2 = 8. - Another schedule: Day 1 do job
[6]
(difficulty = 6), Day 2 do jobs[5, 4, 3, 2, 1]
(difficulty = 5). Total = 6 + 5 = 11.
Return the minimum possible total difficulty. If it's impossible to schedule all jobs in d
days (when there are fewer jobs than days), return -1
.
Intuition
Since jobs must be completed in order and we need to assign them to exactly d
days, this problem is about finding optimal partition points. We need to decide where to "cut" the job sequence to separate work for different days.
The key insight is that this has optimal substructure - if we know the minimum difficulty to complete the first k-1
jobs in j-1
days, we can extend this to find the minimum difficulty for completing i
jobs in j
days by trying all possible ways to schedule jobs [k..i]
on day j
.
Think of it this way: On the last day (day j
), we must complete some consecutive sequence of jobs ending at job i
. We could choose to do just job i
, or jobs [i-1, i]
, or jobs [i-2, i-1, i]
, and so on. For each choice, the difficulty of day j
is the maximum difficulty among those jobs.
This naturally leads to dynamic programming where f[i][j]
represents the minimum total difficulty to complete the first i
jobs using exactly j
days.
For each state f[i][j]
, we try all possible "starting points" k
for the last day's work. If we do jobs [k..i]
on day j
, then:
- We need
f[k-1][j-1]
(minimum difficulty to complete firstk-1
jobs inj-1
days) - Plus the difficulty of day
j
, which ismax(jobDifficulty[k-1], ..., jobDifficulty[i-1])
By trying all valid values of k
and taking the minimum, we find the optimal way to schedule the first i
jobs in j
days.
The base case is f[0][0] = 0
(no jobs, no days, zero difficulty), and we build up the solution incrementally until we reach f[n][d]
, which gives us the answer for all n
jobs in d
days.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the dynamic programming solution using a 2D array f
where f[i][j]
represents the minimum difficulty to complete the first i
jobs in exactly j
days.
Initialization:
- Create a 2D array
f
of size(n+1) × (d+1)
initialized with infinity values - Set
f[0][0] = 0
as the base case (0 jobs in 0 days has 0 difficulty)
State Transition:
For each state f[i][j]
, we iterate through all possible ways to partition the jobs:
i
represents the number of jobs we're considering (from 1 to n)j
represents the number of days we're using (from 1 to min(d, i))k
represents the starting job index for the last day (we iterate k from i down to 1)
For each combination of (i, j, k)
:
- We calculate the maximum difficulty
mx
for jobs[k..i]
which would be done on dayj
- We update
f[i][j]
with the minimum of its current value andf[k-1][j-1] + mx
The state transition equation is:
f[i][j] = min(f[i][j], f[k-1][j-1] + max(jobDifficulty[k-1], ..., jobDifficulty[i-1]))
Implementation Details:
- The outer loop iterates through jobs:
for i in range(1, n + 1)
- The middle loop iterates through days:
for j in range(1, min(d + 1, i + 1))
- We use
min(d + 1, i + 1)
because we can't have more days than jobs
- We use
- The inner loop tries all partition points:
for k in range(i, 0, -1)
- We iterate backwards from
i
to1
to efficiently track the maximum difficulty - As we iterate, we maintain
mx = max(mx, jobDifficulty[k-1])
to get the maximum difficulty for jobs[k..i]
- We iterate backwards from
Final Answer:
- If
f[n][d] >= inf
, it means it's impossible to schedule all jobs ind
days, so return-1
- Otherwise, return
f[n][d]
as the minimum difficulty
The time complexity is O(n²d)
where n
is the number of jobs, and the space complexity is O(nd)
for the DP table.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with jobDifficulty = [6, 5, 4, 3]
and d = 2
days.
We'll build a DP table f[i][j]
where i
is the number of jobs completed and j
is the number of days used.
Initialization:
- Create a 5×3 table (since we have 4 jobs and 2 days)
- Initialize all values to infinity except
f[0][0] = 0
Initial table:
j=0 j=1 j=2 i=0 0 inf inf i=1 inf inf inf i=2 inf inf inf i=3 inf inf inf i=4 inf inf inf
Building the DP table:
For i=1 (first job):
- j=1, k=1: Do job 1 on day 1
mx = jobDifficulty[0] = 6
f[1][1] = f[0][0] + 6 = 0 + 6 = 6
For i=2 (first two jobs):
- j=1, k=2: Do jobs 1-2 on day 1
mx = max(6, 5) = 6
f[2][1] = f[0][0] + 6 = 6
- j=1, k=1: Do job 1 on day 1 (invalid - need f[0][0] which requires 0 days for 0 jobs)
- Skip as we'd need to complete job 2 somewhere
- j=2, k=2: Do job 2 on day 2, job 1 on day 1
mx = jobDifficulty[1] = 5
f[2][2] = f[1][1] + 5 = 6 + 5 = 11
- j=2, k=1: Do jobs 1-2 on day 2
mx = max(6, 5) = 6
f[2][2] = min(11, f[0][1] + 6) = 11
(f[0][1] is inf)
For i=3 (first three jobs):
- j=1, k=3,2,1: Do all jobs on day 1
mx = max(6, 5, 4) = 6
f[3][1] = 6
- j=2, k=3: Do job 3 on day 2, jobs 1-2 on day 1
mx = 4
f[3][2] = f[2][1] + 4 = 6 + 4 = 10
- j=2, k=2: Do jobs 2-3 on day 2, job 1 on day 1
mx = max(5, 4) = 5
f[3][2] = min(10, f[1][1] + 5) = min(10, 6 + 5) = 10
- j=2, k=1: Do jobs 1-3 on day 2
mx = max(6, 5, 4) = 6
f[3][2] = min(10, f[0][1] + 6) = 10
(f[0][1] is inf)
For i=4 (all four jobs):
- j=2, k=4: Do job 4 on day 2, jobs 1-3 on day 1
mx = 3
f[4][2] = f[3][1] + 3 = 6 + 3 = 9
- j=2, k=3: Do jobs 3-4 on day 2, jobs 1-2 on day 1
mx = max(4, 3) = 4
f[4][2] = min(9, f[2][1] + 4) = min(9, 6 + 4) = 9
- j=2, k=2: Do jobs 2-4 on day 2, job 1 on day 1
mx = max(5, 4, 3) = 5
f[4][2] = min(9, f[1][1] + 5) = min(9, 6 + 5) = 9
- j=2, k=1: Do all jobs on day 2 (invalid - need at least one job on day 1)
- Skip
Final table:
j=0 j=1 j=2 i=0 0 inf inf i=1 inf 6 inf i=2 inf 6 11 i=3 inf 6 10 i=4 inf inf 9
Answer: f[4][2] = 9
This corresponds to the optimal schedule:
- Day 1: Jobs [6, 5, 4] with difficulty = 6
- Day 2: Job [3] with difficulty = 3
- Total difficulty = 6 + 3 = 9
Solution Implementation
1class Solution:
2 def minDifficulty(self, jobDifficulty: List[int], d: int) -> int:
3 """
4 Find the minimum difficulty of a job schedule.
5 Jobs must be done in order and distributed across d days.
6 Each day's difficulty is the maximum difficulty of jobs done that day.
7
8 Args:
9 jobDifficulty: List of job difficulties
10 d: Number of days to complete all jobs
11
12 Returns:
13 Minimum total difficulty, or -1 if impossible
14 """
15 n = len(jobDifficulty)
16
17 # Edge case: cannot distribute n jobs across d days if n < d
18 if n < d:
19 return -1
20
21 # dp[i][j] represents minimum difficulty to complete first i jobs in j days
22 # Initialize with infinity (impossible state)
23 dp = [[float('inf')] * (d + 1) for _ in range(n + 1)]
24
25 # Base case: 0 jobs in 0 days has 0 difficulty
26 dp[0][0] = 0
27
28 # Fill the DP table
29 for num_jobs in range(1, n + 1):
30 # Can't use more days than jobs available
31 for num_days in range(1, min(d + 1, num_jobs + 1)):
32 max_difficulty = 0
33
34 # Try different split points for the last day
35 # k represents the starting job index for the last day (1-indexed)
36 for k in range(num_jobs, 0, -1):
37 # Update maximum difficulty for jobs from k to num_jobs
38 max_difficulty = max(max_difficulty, jobDifficulty[k - 1])
39
40 # Check if we can complete first (k-1) jobs in (num_days-1) days
41 if dp[k - 1][num_days - 1] != float('inf'):
42 # Update minimum difficulty for completing num_jobs in num_days
43 dp[num_jobs][num_days] = min(
44 dp[num_jobs][num_days],
45 dp[k - 1][num_days - 1] + max_difficulty
46 )
47
48 # Return result: minimum difficulty to complete all n jobs in d days
49 return -1 if dp[n][d] == float('inf') else dp[n][d]
50
1class Solution {
2 public int minDifficulty(int[] jobDifficulty, int d) {
3 // Use a large value to represent impossible states
4 final int INFINITY = 1 << 30;
5
6 // Get the number of jobs
7 int numJobs = jobDifficulty.length;
8
9 // dp[i][j] represents the minimum difficulty to complete first i jobs in j days
10 int[][] dp = new int[numJobs + 1][d + 1];
11
12 // Initialize all states as impossible (infinity)
13 for (int[] row : dp) {
14 Arrays.fill(row, INFINITY);
15 }
16
17 // Base case: 0 jobs in 0 days has 0 difficulty
18 dp[0][0] = 0;
19
20 // Fill the dp table
21 // i represents the number of jobs to complete
22 for (int i = 1; i <= numJobs; i++) {
23 // j represents the number of days to use (cannot exceed i jobs)
24 for (int j = 1; j <= Math.min(d, i); j++) {
25 // Try all possible ways to split the jobs for the last day
26 int maxDifficultyLastDay = 0;
27
28 // k represents the starting job index for the last day (1-indexed)
29 // We process jobs from k to i on the last day
30 for (int k = i; k > 0; k--) {
31 // Update the maximum difficulty for jobs on the last day
32 maxDifficultyLastDay = Math.max(maxDifficultyLastDay, jobDifficulty[k - 1]);
33
34 // Update dp[i][j] by considering:
35 // - Complete first (k-1) jobs in (j-1) days: dp[k-1][j-1]
36 // - Complete jobs from k to i on the last day: maxDifficultyLastDay
37 dp[i][j] = Math.min(dp[i][j], dp[k - 1][j - 1] + maxDifficultyLastDay);
38 }
39 }
40 }
41
42 // Return -1 if impossible, otherwise return the minimum difficulty
43 return dp[numJobs][d] >= INFINITY ? -1 : dp[numJobs][d];
44 }
45}
46
1class Solution {
2public:
3 int minDifficulty(vector<int>& jobDifficulty, int d) {
4 int n = jobDifficulty.size();
5
6 // dp[i][j] represents the minimum difficulty to complete first i jobs in j days
7 // Initialize with a large value (0x3f3f3f3f represents infinity)
8 int dp[n + 1][d + 1];
9 memset(dp, 0x3f, sizeof(dp));
10
11 // Base case: 0 jobs in 0 days has 0 difficulty
12 dp[0][0] = 0;
13
14 // Fill the dp table
15 for (int i = 1; i <= n; ++i) {
16 // Can't split i jobs into more than i days
17 for (int j = 1; j <= min(d, i); ++j) {
18 int maxDifficulty = 0;
19
20 // Try different ways to assign jobs for the j-th day
21 // k represents the starting job index for day j (1-indexed)
22 for (int k = i; k >= 1; --k) {
23 // Update maximum difficulty for jobs from k to i
24 maxDifficulty = max(maxDifficulty, jobDifficulty[k - 1]);
25
26 // Update minimum total difficulty:
27 // dp[k-1][j-1]: difficulty of completing first k-1 jobs in j-1 days
28 // maxDifficulty: difficulty of day j (jobs from k to i)
29 dp[i][j] = min(dp[i][j], dp[k - 1][j - 1] + maxDifficulty);
30 }
31 }
32 }
33
34 // If it's impossible to schedule (value remains infinity), return -1
35 return dp[n][d] == 0x3f3f3f3f ? -1 : dp[n][d];
36 }
37};
38
1/**
2 * Calculates the minimum difficulty of a job schedule.
3 * Jobs must be completed in order and at least one job must be done each day.
4 * The difficulty of a day is the maximum difficulty of jobs done that day.
5 *
6 * @param jobDifficulty - Array of job difficulties in order
7 * @param d - Number of days to complete all jobs
8 * @returns Minimum total difficulty, or -1 if impossible
9 */
10function minDifficulty(jobDifficulty: number[], d: number): number {
11 const numJobs: number = jobDifficulty.length;
12
13 // If there are fewer jobs than days, it's impossible to schedule
14 if (numJobs < d) {
15 return -1;
16 }
17
18 // Large value representing infinity for initialization
19 const INFINITY: number = 1 << 30;
20
21 // dp[i][j] represents minimum difficulty to complete first i jobs in j days
22 // Initialize with infinity to represent impossible states
23 const dp: number[][] = Array.from(
24 { length: numJobs + 1 },
25 () => new Array<number>(d + 1).fill(INFINITY)
26 );
27
28 // Base case: 0 jobs in 0 days has 0 difficulty
29 dp[0][0] = 0;
30
31 // Fill the DP table
32 for (let jobIndex = 1; jobIndex <= numJobs; jobIndex++) {
33 // Can't use more days than jobs available
34 const maxDays: number = Math.min(d, jobIndex);
35
36 for (let dayCount = 1; dayCount <= maxDays; dayCount++) {
37 let maxDifficulty: number = 0;
38
39 // Try different ways to assign jobs to the current day
40 // k represents the starting job index for the current day (1-indexed)
41 for (let startJob = jobIndex; startJob > 0; startJob--) {
42 // Update max difficulty for jobs from startJob to jobIndex
43 maxDifficulty = Math.max(maxDifficulty, jobDifficulty[startJob - 1]);
44
45 // Only consider valid previous states (need at least dayCount-1 jobs for dayCount-1 days)
46 if (startJob - 1 >= dayCount - 1) {
47 // Update minimum difficulty: previous days + current day
48 dp[jobIndex][dayCount] = Math.min(
49 dp[jobIndex][dayCount],
50 dp[startJob - 1][dayCount - 1] + maxDifficulty
51 );
52 }
53 }
54 }
55 }
56
57 // Return result if valid, otherwise -1
58 return dp[numJobs][d] < INFINITY ? dp[numJobs][d] : -1;
59}
60
Time and Space Complexity
Time Complexity: O(n² × d)
The code uses three nested loops:
- The outer loop iterates through
i
from1
ton
(n iterations) - The middle loop iterates through
j
from1
tomin(d + 1, i + 1)
(at most d iterations) - The inner loop iterates through
k
fromi
down to1
(at most i iterations, which can be up to n)
For each combination of (i, j)
, the inner loop performs O(i)
operations. Since i
can be at most n
, and we have at most n × d
combinations of (i, j)
, the total time complexity is O(n × d × n) = O(n² × d)
.
Space Complexity: O(n × d)
The code creates a 2D array f
with dimensions (n + 1) × (d + 1)
. This requires O((n + 1) × (d + 1)) = O(n × d)
space. The only other variables used are loop counters and the variable mx
, which require O(1)
additional space. Therefore, the overall space complexity is O(n × d)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Loop Bounds for Split Points
A common mistake is using incorrect bounds when iterating through split points for the last day. Many implementations incorrectly start from k = num_jobs - 1
or use wrong termination conditions.
Incorrect Implementation:
# Wrong: Starting from num_jobs - 1
for k in range(num_jobs - 1, 0, -1):
max_difficulty = max(max_difficulty, jobDifficulty[k])
# This skips considering all num_jobs on the last day
Correct Implementation:
# Correct: Starting from num_jobs to consider all possible splits
for k in range(num_jobs, 0, -1):
max_difficulty = max(max_difficulty, jobDifficulty[k - 1])
2. Off-by-One Errors in Index Mapping
The DP table uses 1-based indexing for conceptual clarity (dp[i][j] means first i jobs), but the jobDifficulty array uses 0-based indexing. This frequently leads to confusion.
Incorrect Implementation:
# Wrong: Using k directly as array index
for k in range(num_jobs, 0, -1):
max_difficulty = max(max_difficulty, jobDifficulty[k]) # IndexError!
Correct Implementation:
# Correct: Adjusting for 0-based array indexing
for k in range(num_jobs, 0, -1):
max_difficulty = max(max_difficulty, jobDifficulty[k - 1])
3. Inefficient Maximum Calculation
Recalculating the maximum difficulty from scratch for each split point leads to unnecessary time complexity.
Inefficient Implementation:
for k in range(num_jobs, 0, -1):
# Recalculating max from scratch each time - O(n) operation
max_difficulty = max(jobDifficulty[k-1:num_jobs])
dp[num_jobs][num_days] = min(
dp[num_jobs][num_days],
dp[k - 1][num_days - 1] + max_difficulty
)
Efficient Implementation:
max_difficulty = 0
for k in range(num_jobs, 0, -1):
# Incrementally update max - O(1) operation
max_difficulty = max(max_difficulty, jobDifficulty[k - 1])
dp[num_jobs][num_days] = min(
dp[num_jobs][num_days],
dp[k - 1][num_days - 1] + max_difficulty
)
4. Forgetting to Check Valid Previous States
Not verifying whether the previous state is reachable before using it in the transition can lead to incorrect results.
Incorrect Implementation:
# Wrong: Not checking if previous state is valid
dp[num_jobs][num_days] = min(
dp[num_jobs][num_days],
dp[k - 1][num_days - 1] + max_difficulty
)
Correct Implementation:
# Correct: Only use valid previous states
if dp[k - 1][num_days - 1] != float('inf'):
dp[num_jobs][num_days] = min(
dp[num_jobs][num_days],
dp[k - 1][num_days - 1] + max_difficulty
)
5. Incorrect Day Limit in Loop
Using range(1, d + 1)
for all job counts wastes computation and can cause logical errors when there are fewer jobs than days.
Incorrect Implementation:
# Wrong: Always iterating up to d days regardless of job count
for num_days in range(1, d + 1):
# This allows invalid states like 2 jobs in 5 days
Correct Implementation:
# Correct: Limiting days to available jobs
for num_days in range(1, min(d + 1, num_jobs + 1)):
# Ensures we never have more days than jobs
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!