🧠
Memoization with Sorting and Binary Search
💡 Memoization avoids recomputing states by caching results, making the solution efficient enough for large inputs.
Intuition
Sort jobs by end time, then for each job, use binary search to find the last non-overlapping job and recursively compute max profit with memoization.
Algorithm
- Sort jobs by end time.
- For each job, use binary search to find the last job that doesn't overlap.
- Use recursion with memoization to compute max profit including or excluding current job.
- Return the maximum profit from all jobs.
💡 Memoization prunes the recursion tree drastically, and binary search efficiently finds compatible jobs.
Recurrence:dp[i] = max(dp[i-1], profit[i] + dp[lastNonConflictingJob(i)])
from typing import List
import bisect
def jobScheduling(startTime: List[int], endTime: List[int], profit: List[int]) -> int:
jobs = sorted(zip(startTime, endTime, profit), key=lambda x: x[1])
n = len(jobs)
ends = [job[1] for job in jobs]
memo = {}
def dfs(i):
if i < 0:
return 0
if i in memo:
return memo[i]
# Find last job that ends before jobs[i] starts
start_i = jobs[i][0]
idx = bisect.bisect_right(ends, start_i - 1) - 1
take = jobs[i][2] + dfs(idx)
skip = dfs(i - 1)
memo[i] = max(take, skip)
return memo[i]
return dfs(n - 1)
# Driver code
if __name__ == '__main__':
print(jobScheduling([1,2,3,4,6], [3,5,10,6,9], [20,20,100,70,60])) # Expected 150
Line Notes
jobs = sorted(zip(startTime, endTime, profit), key=lambda x: x[1])Sort jobs by end time for DP order
ends = [job[1] for job in jobs]Extract end times for binary search
idx = bisect.bisect_right(ends, start_i - 1) - 1Find last non-overlapping job index using binary search
memo[i] = max(take, skip)Cache result to avoid recomputation
import java.util.*;
public class Solution {
static class Job {
int start, end, profit;
Job(int s, int e, int p) { start = s; end = e; profit = p; }
}
public static 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]);
}
Arrays.sort(jobs, Comparator.comparingInt(j -> j.end));
int[] ends = new int[n];
for (int i = 0; i < n; i++) ends[i] = jobs[i].end;
Map<Integer, Integer> memo = new HashMap<>();
return dfs(jobs, ends, n - 1, memo);
}
private static int dfs(Job[] jobs, int[] ends, int i, Map<Integer, Integer> memo) {
if (i < 0) return 0;
if (memo.containsKey(i)) return memo.get(i);
int start_i = jobs[i].start;
int idx = binarySearch(ends, start_i - 1);
int take = jobs[i].profit + dfs(jobs, ends, idx, memo);
int skip = dfs(jobs, ends, i - 1, memo);
int res = Math.max(take, skip);
memo.put(i, res);
return res;
}
private static int binarySearch(int[] ends, int target) {
int low = 0, high = ends.length - 1, res = -1;
while (low <= high) {
int mid = (low + high) / 2;
if (ends[mid] <= target) {
res = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return res;
}
public static void main(String[] args) {
int[] startTime = {1,2,3,4,6};
int[] endTime = {3,5,10,6,9};
int[] profit = {20,20,100,70,60};
System.out.println(jobScheduling(startTime, endTime, profit)); // Expected 150
}
}
Line Notes
Arrays.sort(jobs, Comparator.comparingInt(j -> j.end));Sort jobs by end time for DP order
int[] ends = new int[n];Store end times for binary search
int idx = binarySearch(ends, start_i - 1);Find last non-overlapping job index
memo.put(i, res);Cache computed result to avoid recomputation
#include <bits/stdc++.h>
using namespace std;
struct Job {
int start, end, profit;
Job(int s, int e, int p) : start(s), end(e), profit(p) {}
};
int binarySearch(const vector<int>& ends, int target) {
int low = 0, high = (int)ends.size() - 1, res = -1;
while (low <= high) {
int mid = (low + high) / 2;
if (ends[mid] <= target) {
res = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return res;
}
int dfs(const vector<Job>& jobs, const vector<int>& ends, int i, unordered_map<int,int>& memo) {
if (i < 0) return 0;
if (memo.count(i)) return memo[i];
int start_i = jobs[i].start;
int idx = binarySearch(ends, start_i - 1);
int take = jobs[i].profit + dfs(jobs, ends, idx, memo);
int skip = dfs(jobs, ends, i - 1, memo);
return memo[i] = max(take, skip);
}
int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
int n = (int)startTime.size();
vector<Job> jobs;
for (int i = 0; i < n; i++) {
jobs.emplace_back(startTime[i], endTime[i], profit[i]);
}
sort(jobs.begin(), jobs.end(), [](const Job& a, const Job& b) {
return a.end < b.end;
});
vector<int> ends(n);
for (int i = 0; i < n; i++) ends[i] = jobs[i].end;
unordered_map<int,int> memo;
return dfs(jobs, ends, n - 1, memo);
}
int main() {
vector<int> startTime = {1,2,3,4,6};
vector<int> endTime = {3,5,10,6,9};
vector<int> profit = {20,20,100,70,60};
cout << jobScheduling(startTime, endTime, profit) << '\n'; // Expected 150
return 0;
}
Line Notes
sort(jobs.begin(), jobs.end(), ...Sort jobs by end time for DP order
int idx = binarySearch(ends, start_i - 1);Find last compatible job index using binary search
if (memo.count(i)) return memo[i];Return cached result if available
return memo[i] = max(take, skip);Store and return max profit for job i
function jobScheduling(startTime, endTime, profit) {
const jobs = startTime.map((s, i) => [s, endTime[i], profit[i]]);
jobs.sort((a, b) => a[1] - b[1]);
const ends = jobs.map(j => j[1]);
const n = jobs.length;
const memo = new Map();
function binarySearch(target) {
let low = 0, high = n - 1, res = -1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
if (ends[mid] <= target) {
res = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return res;
}
function dfs(i) {
if (i < 0) return 0;
if (memo.has(i)) return memo.get(i);
const start_i = jobs[i][0];
const idx = binarySearch(start_i - 1);
const take = jobs[i][2] + dfs(idx);
const skip = dfs(i - 1);
const res = Math.max(take, skip);
memo.set(i, res);
return res;
}
return dfs(n - 1);
}
// Test
console.log(jobScheduling([1,2,3,4,6], [3,5,10,6,9], [20,20,100,70,60])); // Expected 150
Line Notes
jobs.sort((a, b) => a[1] - b[1]);Sort jobs by end time for DP order
const ends = jobs.map(j => j[1]);Extract end times for binary search
const idx = binarySearch(start_i - 1);Find last non-overlapping job index
memo.set(i, res);Cache computed result to avoid recomputation
Sorting takes O(n log n), binary search O(log n) per job, and memoization ensures each state is computed once.
💡 For n=10^5, this approach is efficient and practical.
Interview Verdict: Accepted
This approach is optimal for large inputs and commonly expected in interviews.