Maximum Profit in Job Scheduling

HardDynamic Programming · Binary Search · Intervals🧩 Grid DPLeetCode #1235 ↗Updated

Problem

There are n jobs; job i occupies the time interval [startTime[i], endTime[i]) and pays profit[i]. Choose a set of jobs whose intervals do not overlap and return the largest total profit. A job that ends at time x is compatible with a job that starts at x.
Inputstart = [1,2,3,3], end = [3,4,5,6], profit = [50,10,40,70]Output120 (jobs 1 and 4)
Inputstart = [1,2,3,4,6], end = [3,5,10,6,9], profit = [20,20,100,70,60]Output150
Inputstart = [1,1,1], end = [2,3,4], profit = [5,6,4]Output6

1 ≤ n ≤ 5 × 104  ·  1 ≤ start[i] < end[i] ≤ 109  ·  1 ≤ profit[i] ≤ 104

Try it yourself

Java
class Solution {
    public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Sort jobs by end time. The best profit using the first i jobs either skips job i or takes it plus the best profit of all jobs that finish by its start — and that prefix length is found by binary search on the sorted end times.
PatternGrid DP

Instinct Grid moves that never loop back (right/down, row to row) plus "count or optimise the paths" - build dp[r][c] from the cells that can step in.

Memory hack Every cell asks its parents - a cell's answer comes only from the neighbours that can move into it, so fill cells in the order they arrive.

Open the Grid DP guide →

Approaches

Brute ForceTake-or-Skip Recursion

Time O(n · 2n)Space O(n)

Sort by start time and decide job by job. Skipping moves to the next job; taking it earns its profit and jumps to the first later job that starts no earlier than its end (found by a linear scan). Every compatible subset is explored.

Java
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
    int n = startTime.length;
    int[][] jobs = new int[n][];
    for (int i = 0; i < n; i++) jobs[i] = new int[] {startTime[i], endTime[i], profit[i]};
    Arrays.sort(jobs, (a, b) -> Integer.compare(a[0], b[0]));   // by start time
    return best(jobs, 0);
}

// Best profit using only jobs[i..n-1]
private int best(int[][] jobs, int i) {
    if (i == jobs.length) return 0;
    int next = i + 1;
    while (next < jobs.length && jobs[next][0] < jobs[i][1]) next++;   // first compatible
    int take = jobs[i][2] + best(jobs, next);
    int skip = best(jobs, i + 1);
    return Math.max(take, skip);
}

Simple to reason about, but the subset tree is exponential.

At a glance

LevelApproachTimeSpace
Brute ForceTake-or-Skip RecursionO(n · 2n)O(n)
BetterMemoized Recursion (Linear Next Lookup)O(n2)O(n)
OptimalSort by End + DP + Binary SearchO(n log n)O(n)
Common pitfalls
  • Treating touching intervals (end == start) as overlapping.
  • Sorting the three arrays separately — job attributes must move together (sort indices or tuples).
  • Greedy “earliest finish” only maximises the count of jobs, not the profit.
Interview tips
  • Name it as weighted interval scheduling: take-or-skip plus “latest compatible job”.
  • Explain why the compatible jobs form a prefix once sorted by end time.
  • A TreeMap of end → best profit (floorEntry) is an equally valid O(n log n) variant.
Pattern
Weighted interval scheduling → sort by end, DP over prefixes, binary-search the last non-overlapping job.
My notes saved in this browser