Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1335. Minimum Difficulty of a Job Schedule - Leetcode Solution

Problem Description

You are given an array jobDifficulty of length n, where each element represents the difficulty of a job scheduled in a specific order. You are also given an integer d representing the number of days you must schedule all jobs.

Each day, you must assign at least one job, and jobs assigned on a day must be a consecutive sequence from the array. The difficulty of a day is defined as the maximum difficulty among the jobs scheduled that day. The total difficulty of the schedule is the sum of each day's difficulty.

Your task is to split the jobs into d days such that the total difficulty is minimized. If it is not possible to schedule all jobs in d days (i.e., n < d), return -1.

Constraints:

  • Each job must be scheduled exactly once and in the given order.
  • Each day must have at least one job.
  • You cannot skip or reorder jobs.
  • Return a single integer representing the minimum total difficulty, or -1 if impossible.

Thought Process

The problem is about partitioning the jobDifficulty array into d contiguous segments (days), where each segment has at least one job, and minimizing the sum of the maximum job difficulty in each segment.

A brute-force approach would consider all possible ways to split the array into d segments, compute the sum of maximums for each, and return the minimum. However, the number of possible splits grows exponentially with the array size, making brute-force infeasible for larger inputs.

This problem has overlapping subproblems and optimal substructure, suggesting that dynamic programming (DP) is an effective strategy. By breaking the problem into smaller subproblems—such as "what is the minimum total difficulty for scheduling the first i jobs in k days?"—we can build up the solution efficiently.

The main challenge is efficiently computing the maximum difficulty for each possible segment and managing the DP state transitions.

Solution Approach

We use dynamic programming to solve this problem efficiently. Let's break down the approach step by step:

  1. DP State Definition:
    • Let dp[i][k] represent the minimum total difficulty to schedule the first i jobs in k days.
  2. Base Case:
    • If i = 0 and k = 0, then dp[0][0] = 0 (no jobs, no days, zero difficulty).
    • If i < k, it's impossible to schedule, so set dp[i][k] = inf.
  3. Transition:
    • To compute dp[i][k], try all possible previous split points j where the last day takes jobs j to i-1 (since arrays are 0-based).
    • For each j from k-1 to i-1:
      • Compute the maximum difficulty of jobs j to i-1.
      • Update dp[i][k] = min(dp[i][k], dp[j][k-1] + max_difficulty).
  4. Final Answer:
    • The answer is dp[n][d], where n is the length of jobDifficulty.
  5. Optimization:
    • To avoid recomputing maximums, keep a running maximum as you iterate backward from i-1 to k-1 for each day.
  6. Edge Case:
    • If n < d, return -1 immediately.

This DP approach ensures we consider all valid splits and efficiently compute the minimum total difficulty.

Example Walkthrough

Input: jobDifficulty = [6,5,4,3,2,1], d = 2

We need to split the jobs into 2 days. Let's walk through the solution:

  1. Possible Splits:
    • Day 1: jobs [6], Day 2: [5,4,3,2,1] → Day 1 max: 6, Day 2 max: 5 → Total: 6+5=11
    • Day 1: jobs [6,5], Day 2: [4,3,2,1] → Day 1 max: 6, Day 2 max: 4 → Total: 6+4=10
    • Day 1: jobs [6,5,4], Day 2: [3,2,1] → Day 1 max: 6, Day 2 max: 3 → Total: 6+3=9
    • Day 1: jobs [6,5,4,3], Day 2: [2,1] → Day 1 max: 6, Day 2 max: 2 → Total: 6+2=8
    • Day 1: jobs [6,5,4,3,2], Day 2: [1] → Day 1 max: 6, Day 2 max: 1 → Total: 6+1=7
  2. Minimum Total Difficulty:
    • The minimum is 7, achieved by splitting after the 5th element.
  3. DP Table:
    • The DP approach will fill dp[i][k] for all i and k, and finally return dp[6][2] = 7.

Time and Space Complexity

  • Brute-force:
    • Number of ways to split n jobs into d parts is exponential, leading to O(2^n) time complexity.
  • Dynamic Programming:
    • There are O(n*d) DP states.
    • For each state, we look back at up to n previous jobs, leading to O(n^2*d) total time complexity.
    • Space complexity is O(n*d) for the DP table.
  • Optimizations:
    • With further optimizations, such as using 1D DP arrays or segment trees, space and time can sometimes be improved, but O(n^2*d) is standard and acceptable for constraints.

Summary

To solve the Minimum Difficulty of a Job Schedule problem, we use dynamic programming to efficiently explore all valid ways to partition the jobs into days, minimizing the sum of daily maximum difficulties. By carefully defining our DP state and transitions, we avoid redundant calculations and ensure an optimal solution, even for larger inputs. This approach elegantly balances exhaustive search with computational efficiency, making it a classic example of DP in action.

Code Implementation

from typing import List

class Solution:
    def minDifficulty(self, jobDifficulty: List[int], d: int) -> int:
        n = len(jobDifficulty)
        if n < d:
            return -1
        dp = [[float('inf')] * (d + 1) for _ in range(n + 1)]
        dp[0][0] = 0
        for i in range(1, n + 1):
            for k in range(1, min(d, i) + 1):
                maxd = 0
                for j in range(i - 1, k - 2, -1):
                    maxd = max(maxd, jobDifficulty[j])
                    if dp[j][k - 1] != float('inf'):
                        dp[i][k] = min(dp[i][k], dp[j][k - 1] + maxd)
        return dp[n][d] if dp[n][d] != float('inf') else -1
      
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution {
public:
    int minDifficulty(vector<int>& jobDifficulty, int d) {
        int n = jobDifficulty.size();
        if (n < d) return -1;
        vector<vector<int>> dp(n + 1, vector<int>(d + 1, INT_MAX));
        dp[0][0] = 0;
        for (int i = 1; i <= n; ++i) {
            for (int k = 1; k <= min(d, i); ++k) {
                int maxd = 0;
                for (int j = i - 1; j >= k - 1; --j) {
                    maxd = max(maxd, jobDifficulty[j]);
                    if (dp[j][k - 1] != INT_MAX) {
                        dp[i][k] = min(dp[i][k], dp[j][k - 1] + maxd);
                    }
                }
            }
        }
        return dp[n][d] == INT_MAX ? -1 : dp[n][d];
    }
};
      
import java.util.*;

class Solution {
    public int minDifficulty(int[] jobDifficulty, int d) {
        int n = jobDifficulty.length;
        if (n < d) return -1;
        int[][] dp = new int[n + 1][d + 1];
        for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE);
        dp[0][0] = 0;
        for (int i = 1; i <= n; ++i) {
            for (int k = 1; k <= Math.min(d, i); ++k) {
                int maxd = 0;
                for (int j = i - 1; j >= k - 1; --j) {
                    maxd = Math.max(maxd, jobDifficulty[j]);
                    if (dp[j][k - 1] != Integer.MAX_VALUE) {
                        dp[i][k] = Math.min(dp[i][k], dp[j][k - 1] + maxd);
                    }
                }
            }
        }
        return dp[n][d] == Integer.MAX_VALUE ? -1 : dp[n][d];
    }
}
      
/**
 * @param {number[]} jobDifficulty
 * @param {number} d
 * @return {number}
 */
var minDifficulty = function(jobDifficulty, d) {
    const n = jobDifficulty.length;
    if (n < d) return -1;
    const dp = Array.from({length: n + 1}, () => Array(d + 1).fill(Infinity));
    dp[0][0] = 0;
    for (let i = 1; i <= n; ++i) {
        for (let k = 1; k <= Math.min(d, i); ++k) {
            let maxd = 0;
            for (let j = i - 1; j >= k - 1; --j) {
                maxd = Math.max(maxd, jobDifficulty[j]);
                if (dp[j][k - 1] !== Infinity) {
                    dp[i][k] = Math.min(dp[i][k], dp[j][k - 1] + maxd);
                }
            }
        }
    }
    return dp[n][d] === Infinity ? -1 : dp[n][d];
};