# Find non-overlapping scheduled jobs with maximum cost

Given a set of n jobs with [start time, end time, cost] find a subset so that no 2 jobs overlap and the cost is maximum.

Now I'm not sure if a greedy algorithm will do the trick. That is, sort by cost and always take the next job that doesn't intersect and with max cost between the two.

Is this equivalent to a knapsack problem? How could I approach it?

The graph of overlapping jobs is an interval graph. Interval graphs are perfect graphs. So what you are trying to do is find a maximum weight independent set (i.e., no two overlap) in a perfect graph. This can be solved in polynomial time. The algorithm is given in "Polynomial Algorithms for Perfect Graphs", by M. Grötschel, L. Lovász, and A. Schrijver.

There are a number of special cases of perfect graphs for which people have discovered simpler and more efficient algorithms for this task. I don't know whether interval graphs fall into one of these special cases.

Greedy algorithm can't help in that case. And it couldn't be compared with both fractional or 0-1 knapsack problems. The first could be resolved by greedy algorithm in O(n) and the second is NP.

The problem you have could be brute-forced in O(2^n). But you could optimize it using dynamic programming.

1) Sort intervals by start time.

2) Initialize int[] costs = new int[jobs.length] with Integer.MIN_VALUE (or any negative value);

3) Define follow recursive routine (here is Java):

private int findCost(Job[] jobs, int k, int[] costs) {
if(k >= jobs.length) {
return 0;
}
if(costs[k] < 0) {
int x = findNextCompatibleJob(jobs, k);
int sumK = jobs[k].cost + findCost(jobs, x, costs);
int sumK1 = findCost(jobs, k + 1, costs);
costs[k] = Math.max(sumK, sumK1);
}
return costs[k];
}

private int findNextCompatibleJob(Job[] jobs, int k) {
int finish = jobs[k].finish;
for(int i = k + 1; i < jobs.length; i++) {
if(jobs[i].start > finish) {
return i;
}
}
return Integer.MAX_VALUE;
}


4) Start recursion with k = 0;

I have implemented only recursion routine while other parts are trivial. I considered that any cost is >= 0. If there could be negative cost jobs we need to add check for that and just pass that jobs without consideration.

One could implement this in O(nlogn)

Steps:

1. Sort the intervals based on end time
2. define p(i) for each interval, giving the biggest end point which is smaller than the start point of i-th interval. Use binary search to obtain nlogn
3. define d[i] = max(w(i) + d[p(i)], d[i-1]).

initialize d = 0

The result will be in d[n] n- the number of intervals.

Overall complexity O(nlogn)

import java.util.*;
class Interval {
public int start;
public int end;
public int cost;
public Interval(int start, int end, int cost){
this.start = start;
this.end = end;
this.cost = cost;
}
}
public class BestCombinationFinder {
public int getBestCombination(List<Interval> intervals) {
if (intervals == null || intervals.size() == 0) {
return 0;
}
Collections.sort(intervals, new Comparator<Interval>() {
public int compare(Interval i1, Interval i2) {
if (i1.end < i2.end) {
return -1;
}
else if (i1.end > i2.end) {
return 1;
}
return 0;
}
});
return findBestCombination(intervals);
}
private int findBestCombination(List<Interval> intervals) {
int[] dp = new int[intervals.size() + 1];
for (int i = 1; i <= intervals.size(); i++) {
Interval currInt = intervals.get(i - 1);
int pIndex = find(intervals, currInt.start, 0, intervals.size() - 1);
dp[i] = Math.max(dp[pIndex+1] + currInt.cost, dp[i - 1]);
}
return dp[intervals.size()];
}
private int find(List<Interval> intervals, int target, int left, int right) {
if (left > right) {
return right;
}
else {
int mid = (left + right) / 2;
if (intervals.get(mid).end == target) {
return mid;
}
else if (intervals.get(mid).end > target) {
return find(intervals, target, left, mid - 1);
}
else {
return find(intervals, target, mid + 1, right);
}
}
}
}

• Please give pseudocode, rather than requiring people to understand Java (and, in particular, Java collections). – David Richerby Apr 16 '14 at 17:54
• I have added the pseudo-code in the first part of my answer. I just wanted to add the corresponding Java code as well if it helps anyone understand it better. – Szilard Mandici Apr 16 '14 at 19:52

Yes, it is equivalent to a knapsack problem. Consider job's end time and prepare the table like a knapsack. Before reading following solution please check out knapsack problem and its solution.

// Input:
// Jobs (stored in array jobs)
// Number of jobs (n)

find the maximum end time from given n jobs => max_end

for j from 0 to max_end do
table[0, j] := 0
end for

for i from 1 to n do
for j from 0 to max_end do
if jobs[i].end <= j then
table[i, j] := max(table[i-1, j], table[i-1, jobs[i].start] + jobs[i].cost)
else
table[i, j] := table[i-1, j]
end if
end for
end for


You can also print the scheduled jobs by traversing the table:

j := max_end;
for i from n to 1 do
if table[i][j] != table[i-1][j]
print jobs[i]
j = jobs[i].start;
end if
end for


Complexity is same as knapsack problem.