Description
Maximum Profit in Job Scheduling
We have n jobs, where every job is scheduled to be done from startTime[i] to endTime[i], obtaining a profit of profit[i].
You’re given the startTime, endTime and profit arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range.
If you choose a job that ends at time X you will be able to start another job that starts at time X.
Example 1:

Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]
Output: 120
Explanation: The subset chosen is the first and fourth job.
Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70.
Example 2:

Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]
Output: 150
Explanation: The subset chosen is the first, fourth and fifth job.
Profit obtained 150 = 20 + 70 + 60.
Example 3:

Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]
Output: 6
Constraints:
1 <= startTime.length == endTime.length == profit.length <= 5 * 1041 <= startTime[i] < endTime[i] <= 1091 <= profit[i] <= 104
Approach
- We create a class for better handling
- Create list with job sorted by end time
- Use Tree Map
dpto use methods likefloorKeywhich returns greatest key less than equal to key andlastEntrywhich is mapping associated with greatest key - Time Complexity: O(n log n)
Sorting jobs: O(n log n)
For each job:TreeMap.floorKeyand put operations → O(log n)
Total: O(n log n) - Space Complexity: O(n)
jobs list: O(n)
TreeMapto store at most n entries: O(n)
class Solution {
private class Job {
int start, end, profit;
Job(int s, int e, int p) {
start = s;
end = e;
profit = p;
}
}
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
List<Job> jobs = new ArrayList<>();
for (int i = 0; i < startTime.length; i++) {
jobs.add(new Job(startTime[i], endTime[i], profit[i]));
}
jobs.sort(Comparator.comparingInt(j -> j.end)); // sort by end time asc
TreeMap<Integer, Integer> dp = new TreeMap<>();
dp.put(0,0);
for (Job job: jobs) {
int prevTime = dp.floorKey(job.start);
int currProfit = dp.get(prevTime) + job.profit;
if (currProfit > dp.lastEntry().getValue()) {
dp.put(job.end, currProfit);
}
}
return dp.lastEntry().getValue();
}
}Primary Approach: Dynamic Programming + Binary Search ( Time, Space)
Intuition
To solve 1235. Maximum Profit in Job Scheduling, we combine Dynamic Programming with Binary Search (Weighted Interval Scheduling):
- Sort by End Time: Bundle each job’s
startTime,endTime, andprofitinto an object and sort all jobs in ascending order by their end time. - DP Definition: Define
dp[i]as the maximum profit achievable using a subset of the firstijobs (-indexed). - Transition for Job
i - 1:- Option 1 (Exclude Job ): Don’t pick the current job
dp[i - 1]. - Option 2 (Include Job ): Pick the current job
job.profit + dp[latestCompatible + 1], wherelatestCompatibleis the index of the latest job that ends on or beforejob.startTime.
- Option 1 (Exclude Job ): Don’t pick the current job
- Binary Search: Since jobs are sorted by
endTime, use binary search (upper_bound/floor) to efficiently findlatestCompatiblein time.
import java.util.Arrays;
class Solution {
static class Job {
int start, end, profit;
Job(int start, int end, int profit) {
this.start = start;
this.end = end;
this.profit = profit;
}
}
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
int n = startTime.length;
Job[] jobs = new Job[n];
for (int i = 0; i < n; i++) {
jobs[i] = new Job(startTime[i], endTime[i], profit[i]);
}
// Sort jobs by end time
Arrays.sort(jobs, (a, b) -> Integer.compare(a.end, b.end));
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int currentProfit = jobs[i - 1].profit;
// Binary search for the last non-overlapping job (end <= start)
int prevIndex = binarySearch(jobs, i - 1);
int includeProfit = currentProfit + (prevIndex != -1 ? dp[prevIndex + 1] : 0);
int excludeProfit = dp[i - 1];
dp[i] = Math.max(includeProfit, excludeProfit);
}
return dp[n];
}
private int binarySearch(Job[] jobs, int index) {
int low = 0, high = index - 1;
int result = -1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (jobs[mid].end <= jobs[index].start) {
result = mid;
low = mid + 1; // Try to find a later compatible job
} else {
high = mid - 1;
}
}
return result;
}
}
Complexity
- Time Complexity: — Sorting takes , and iterations performing binary searches take .
- Space Complexity: — Space for storing custom
Jobobjects and thedparray.
Why i - 1 is Used Everywhere
The core reason for i - 1 is the offset between 1-based Dynamic Programming states and 0-based Java array indexing.
The Index Mapping
dpArray (1-based, size ):dp[i]represents the maximum profit achievable considering the first jobs.dp[0]= Base case (0 jobs considered = 0 profit).dp[1]= Max profit considering the 1st job.dp[n]= Max profit considering all jobs (our final answer).
jobsArray (0-based, size ): In 0-indexed Java arrays, the -thjob is located at indexi - 1.
Step-by-Step Breakdown of the Loop
for (int i = 1; i <= n; i++) {
// STEP 1: Fetch profit of the i-th job
int currentProfit = jobs[i - 1].profit;
// STEP 2: Find the latest job that ends before jobs[i - 1] starts
int prevIndex = binarySearch(jobs, i - 1);
// STEP 3: Profit if we INCLUDE jobs[i - 1]
int includeProfit = currentProfit + (prevIndex != -1 ? dp[prevIndex + 1] : 0);
// STEP 4: Profit if we EXCLUDE jobs[i - 1]
int excludeProfit = dp[i - 1];
// STEP 5: Pick the best of both choices for dp[i]
dp[i] = Math.max(includeProfit, excludeProfit);
}
currentProfit = jobs[i - 1].profit
To evaluate the -th job, we accessjobs[i - 1]because the 1st job is atjobs[0], the 2nd job is atjobs[1], etc.prevIndex = binarySearch(jobs, i - 1)
Passesi - 1(the 0-based index of the current job) to binary search to locate the latest compatible non-overlapping job.includeProfit = currentProfit + (prevIndex != -1 ? dp[prevIndex + 1] : 0)
If we INCLUDE the current job:- We earn its
currentProfit. prevIndexis the 0-based index returned by binary search (e.g.,prevIndex = 2means the 3rd job in the array).- Because
dpuses 1-based counting, the maximum profit for the firstprevIndex + 1jobs is stored atdp[prevIndex + 1]. - If
prevIndex == -1(no compatible prior job exists), add0.
- We earn its
excludeProfit = dp[i - 1]
If we EXCLUDE the current job, the maximum profit is simply the maximum profit achieved from the firsti - 1jobs, which is stored indp[i - 1].dp[i] = Math.max(includeProfit, excludeProfit)
Takes the maximum between taking or skipping the current job and stores it indp[i].
Why binarySearch Starts with high = index - 1
private int binarySearch(Job[] jobs, int index) {
int low = 0, high = index - 1; // <--- Why high = index - 1?
int result = -1;
...
- Excludes the Current Job:
indexis the position of the job we are evaluating. A job cannot be non-overlapping with itself, so we must strictly search among jobs beforeindex. - Preserves the Sorted Range: The
jobsarray is sorted byendTime. Any job that ends beforejobs[index].start**must appear at an index smaller thanindex**(i.e., from index0toindex - 1). - Prevents Redundant Lookups: Setting
high = index - 1guarantees thatmidwill never reachindexor beyond, restricting binary search exclusively to earlier jobs.
Alternative Approach: Dynamic Programming + TreeMap ( Time, Space)
Intuition
Instead of maintaining an explicit 1D array, store optimal (endTime, maxProfit) pairs inside a TreeMap:
- Sort jobs by
endTime. - Insert base state
(0, 0)intodp(TreeMap<Integer, Integer>). - For each job
(start, end, profit):- Query
dp.floorEntry(start)to get the maximum profit achievable up tostart. - Calculate total profit if taking this job:
floorEntry.getValue() + profit. - If this new profit strictly exceeds
dp.lastEntry().getValue(), recorddp.put(end, totalProfit).
- Query
import java.util.Arrays;
import java.util.TreeMap;
class Solution {
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
int n = startTime.length;
int[][] jobs = new int[n][3];
for (int i = 0; i < n; i++) {
jobs[i] = new int[]{startTime[i], endTime[i], profit[i]};
}
// Sort by end time
Arrays.sort(jobs, (a, b) -> Integer.compare(a[1], b[1]));
// Key = end time, Value = max profit at or before this end time
TreeMap<Integer, Integer> dp = new TreeMap<>();
dp.put(0, 0);
for (int[] job : jobs) {
int start = job[0], end = job[1], p = job[2];
int totalProfit = dp.floorEntry(start).getValue() + p;
if (totalProfit > dp.lastEntry().getValue()) {
dp.put(end, totalProfit);
}
}
return dp.lastEntry().getValue();
}
}
Complexity
- Time Complexity: — Sorting takes and performing
TreeMaplookups/insertions takes . - Space Complexity: — Space needed for entries in
TreeMap.
Key Interview Discussion Points
- Non-Overlapping Edge Case: If job A ends at and job B starts at , they do not overlap. Ensure the binary search/lookup checks for
jobs[mid].end <= jobs[i].start(inclusive inequality). - Why Sort by End Time? Sorting by
endTimeallows past subproblems (0 ... i-1) to represent finalized maximum profits for time ranges ending before or atjobs[i].start.
Easy Memory Rule
“Sort by End Time Choice:
dp[i-1]vs.profit + dp[BinarySearch(start)]!”