Dynamic Programming · Classic DP Patterns

Weighted Job Scheduling

Sorting jobs by end time, then for each job (processed in that order) deciding whether including it beats skipping it - if included, its value combines with the best achievable using only jobs that finished before it started, found via binary search since end times are already sorted.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: DP over positions
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

You have jobs with start times, end times and profits, and you can work on only one job at a time. Which jobs maximize the total profit? If every job paid the same, a greedy rule would do: always pick the job that ends earliest. With profits, that fails, because one long, valuable job can beat several short ones. The fix is a take-or-skip DP over jobs sorted by end time, with a binary search for the last job that finishes in time.

We'll go from simple to more technical:

  1. The pattern: sort by end, then best[i] = max(skip, profit + best[last compatible]).
  2. A second flavor: a DP over positions when times are small integers.
  3. Where weighted scheduling shows up (bookings, ad slots, rides).
  4. What breaks it: greedy rules, strict comparisons, linear scans, overflow.

The approach

Java

int jobScheduling(int[] start, int[] end, int[] profit) {
    int n = start.length;
    Integer[] order = new Integer[n];
    for (int i = 0; i < n; i++) order[i] = i;
    Arrays.sort(order, (a, b) -> Integer.compare(end[a], end[b]));   // by end time

    int[] ends = new int[n];
    for (int i = 0; i < n; i++) ends[i] = end[order[i]];
    int[] best = new int[n + 1];                     // best[i] = max profit using the first i jobs (by end)
    for (int i = 1; i <= n; i++) {
        int job = order[i - 1];
        int k = lastEndingBy(ends, i - 1, start[job]);   // how many earlier jobs end at or before this start
        best[i] = Math.max(best[i - 1],                    // skip this job
                           best[k] + profit[job]);         // take it, after the compatible ones
    }
    return best[n];
}

// number of jobs among ends[0..limit) whose end <= t (ends is sorted)
int lastEndingBy(int[] ends, int limit, int t) {
    int lo = 0, hi = limit;
    while (lo < hi) {
        int mid = (lo + hi) >>> 1;
        if (ends[mid] <= t) lo = mid + 1; else hi = mid;
    }
    return lo;
}

Python

from bisect import bisect_right


def job_scheduling(start: list[int], end: list[int], profit: list[int]) -> int:
    jobs = sorted(zip(end, start, profit))           # by end time
    ends = [e for e, _, _ in jobs]
    best = [0] * (len(jobs) + 1)                     # best[i] = max profit using the first i jobs (by end)
    for i in range(1, len(jobs) + 1):
        _, s, p = jobs[i - 1]
        k = bisect_right(ends, s, 0, i - 1)          # how many earlier jobs end at or before this start
        best[i] = max(best[i - 1],                   # skip this job
                      best[k] + p)                   # take it, after the compatible ones
    return best[-1]

Sorted by end time, the jobs compatible with job i - those ending by its start - form a prefix of the list, so "the best I can do before this job starts" is just best[k] for the length k of that prefix, found by binary search. In Python that search is one call: bisect.bisect_right(ends, start, 0, i - 1) counts the ends <= start (bisect_left would count only those < start). <= makes a job that ends exactly when another starts compatible, as the problem defines.

A second flavor: DP over positions

When jobs (or rides, bookings) live on a small range of integer positions, you don't need sorting or binary search at all. Walk positions from right to left: at each point, either move on, or take a job that starts here and jump to its end.

Java

long[] best = new long[n + 2];                     // best[p] = most you can still earn from point p
for (int p = n - 1; p >= 1; p--) {
    best[p] = best[p + 1];                         // nothing starts here (or skip it)
    for (int[] r : startingAt.get(p))              // rides {start, end, tip} beginning at p
        best[p] = Math.max(best[p], (long) (r[1] - r[0]) + r[2] + best[r[1]]);
}
return best[1];

Python

best = [0] * (n + 2)                               # best[p] = most you can still earn from point p
for p in range(n - 1, 0, -1):
    best[p] = best[p + 1]                          # nothing starts here (or skip it)
    for start, end, tip in starting_at[p]:         # rides beginning at p
        best[p] = max(best[p], (end - start) + tip + best[end])

That's O(positions + jobs). This is also why "sort by start time" is not wrong in itself: with jobs sorted by start, a DP from the right (take job i and jump to the first job starting at or after its end, or skip it) is equally correct. What must match is the sort order and the direction of the recurrence.

Where you'll see it

Booking systems that must accept or reject overlapping requests for a single resource (a room, a machine, a broadcast slot) to maximize revenue, ad-slot and spectrum auctions, and ride-dispatch problems like the taxi example are weighted interval scheduling. When the timeline is discretized (minutes in a day, days in a season), the positions version is simpler and faster.

Which tool for which job?

You needUse
Max profit, one workersort by end, take or skip, binary search for the last compatible job
Times are small integersa DP over positions from right to left
Max number of jobs, no profitsthe earliest-end greedy, provably optimal
Which jobs were chosenwalk the table back
Two or more workersnot this DP run twice: min-cost flow, or a DP over pairs

Common mistakes

MistakeWhat happensDo this instead
Greedy by earliest end20 instead of 100DP when profits differ
Greedy by highest profit50 instead of 60DP
< instead of <= for compatibilityback-to-back jobs counted as clashingend ≤ next start is compatible
A linear scan for the last compatible jobO(n²)binary search on sorted ends
int totals100,000 big payouts wraplong
Running the one-worker DP twice for two workers93 instead of 97a real two-worker method

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why sort by end time for the take-or-skip recurrence?

Show answer

Then every job compatible with job i (ending by its start) comes before it, in one contiguous prefix of the list. best[k] for that prefix is already computed, and binary search over the sorted end times finds k in O(log n).

2. Jobs (1-2, 10), (2-3, 10), (1-3, 100). What does "earliest end first" give, and what's optimal?

Show answer

Greedy takes 1-2 and 2-3 for 20; the single job 1-3 is worth 100. Earliest-end greedy maximizes the number of jobs, not their total profit.

3. A job ends at 5 and another starts at 5. Compatible?

Show answer

Yes - with half-open intervals [start, end), the first is over when the second begins. The binary search must count jobs with end <= start; using < would wrongly exclude back-to-back jobs.

Key takeaways

  • Weighted interval scheduling is take-or-skip DP over jobs sorted by end time.
  • Compatible earlier jobs form a prefix; binary-search it with end <= start.
  • Greedy rules (earliest end, highest profit) fail when jobs have different profits.
  • With few integer positions, a right-to-left DP over positions needs no sort at all.
  • O(n log n); watch for totals that exceed an int.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Max profit, one workersort by end plus binary search DP
Small integer timesDP over positions
Most jobs, no profitsearliest-end greedy
The chosen jobswalk back
Several workersmin-cost flow / pair DP

Snippets worth knowing by heart

Java

Arrays.sort(jobs, (a, b) -> Integer.compare(a[1], b[1]));       // by end
long[] best = new long[n + 1];
for (int i = 1; i <= n; i++) {
    int k = lastEndingBy(ends, i - 1, jobs[i - 1][0]);          // binary search, end <= start
    best[i] = Math.max(best[i - 1], best[k] + jobs[i - 1][2]);
}

Python

chosen, i = [], len(jobs)                # walk back for the chosen jobs
while i:
    if best[i] == best[i - 1]:
        i -= 1
    else:
        chosen.append(jobs[i - 1])
        i = bisect.bisect_right(ends, jobs[i - 1][0], 0, i - 1)

The traps

  • Greedy by end or by profit: when profits differ, both pick worse sets. Use DP over jobs sorted by end.
  • < instead of <=: back-to-back jobs count as clashing. A job ending at a start time is compatible.
  • Linear scans: finding the last compatible job by scanning is O(n^2). Binary search the sorted ends.
  • int totals: many big payouts wrap. Use long.
  • Two workers solved one at a time: running the one-worker DP twice misses better splits. Use a real two-worker method.

Staff-level points, one line each

  • Walking back returned non-overlapping jobs worth exactly the optimum (300 sets, checked by brute force).
  • Unweighted earliest-end greedy equaled the DP on 300 sets.
  • Two workers: running the DP twice gave 93, and the best is 97.
  • 100,000 big payouts: 38,051,641,146,063 as a long, 2,147,463,098 as an int.

Say it in an interview

  • "Sort by end, then for each job take it or skip it. A binary search finds the last compatible job."
  • "Earliest-end greedy only works when every job is worth the same."
  • "Back-to-back jobs are compatible: end ≤ start."
  • "For multiple workers it's a different problem, not the same DP repeated."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Weighted Job Scheduling free

Maximum Earnings From Taxi

MediumProCoding problemJava · Python

A cab drives one way down a road, from point 1 to point n. Riders ask for trips start end tip, and a trip pays its distance, end - start, plus the tip. The cab holds one rider at a time but can take a new rider at the very point where the last one gets out.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Maximum Profit in Job Scheduling

HardProCoding problemJava · Python

Each job has a start time, an end time and a profit. Choose jobs that don't overlap so the total profit is as large as possible. A job runs over [start, end), so a job ending at 5 doesn't clash with one starting at 5.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Weighted Job Scheduling free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime