Bird
Raised Fist0
Interview Prepdp-knapsackhardAmazonGoogleFacebook

Maximum Profit in Job Scheduling

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
📋
Problem

Imagine you are a freelancer with multiple job offers, each with a start time, end time, and profit. You want to schedule jobs to maximize your total earnings without overlapping any jobs.

Given n jobs where every job is represented as (startTime, endTime, profit), find the maximum profit you can earn by scheduling non-overlapping jobs. You may choose to skip some jobs. Return the maximum profit achievable.

1 ≤ n ≤ 10^51 ≤ startTime[i], endTime[i], profit[i] ≤ 10^9startTime[i] < endTime[i]
Edge cases: All jobs overlap completely → output is max single job profitJobs with same start and end times but different profits → pick highest profit jobJobs sorted in descending order of end time → algorithm must still work
</>
IDE
def jobScheduling(startTime: List[int], endTime: List[int], profit: List[int]) -> int:public int jobScheduling(int[] startTime, int[] endTime, int[] profit)int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit)function jobScheduling(startTime, endTime, profit)
def jobScheduling(startTime, endTime, profit):
    # Write your solution here
    pass
class Solution {
    public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
        // Write your solution here
        return 0;
    }
}
#include <vector>
using namespace std;

int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
    // Write your solution here
    return 0;
}
function jobScheduling(startTime, endTime, profit) {
    // Write your solution here
}
0/9
Common Bugs to Avoid
Wrong: Less than max profit due to greedy scheduling by earliest end timeUsing greedy approach instead of DP with binary search for last non-conflicting jobImplement DP recurrence dp[i] = max(dp[i-1], profit[i] + dp[last_non_conflict]) with binary search to find last non-conflicting job
Wrong: Sum of all profits ignoring overlapsNot enforcing non-overlapping constraint in DP or binary searchEnsure binary search finds last job that ends before current job starts and add dp[last_non_conflict] only if no overlap
Wrong: Incorrect result due to unsorted jobsNot sorting jobs by end time before DP and binary searchSort jobs by end time before processing and binary searching
Wrong: Crash or wrong output on empty inputNo base case handling for empty input arraysAdd base case: if no jobs, return 0 immediately
Wrong: Wrong profit for single job inputDP initialization incorrect for single elementInitialize dp[0] = profit[0] for single job input
Test Cases
t1_01basic
Input{"startTime":[1,2,3,4,6],"endTime":[3,5,10,6,9],"profit":[20,20,100,70,60]}
Expected150

Schedule jobs 1 (1-3, profit 20), 4 (4-6, profit 70), and 5 (6-9, profit 60) for total 150.

t1_02basic
Input{"startTime":[1,3,3,5,6],"endTime":[2,4,6,7,9],"profit":[50,10,40,70,60]}
Expected150

Schedule jobs 1 (1-2, 50), 4 (5-7, 70), and 5 (6-9, 60) overlap but job 4 and 5 overlap, so best is jobs 1,3,5 for total 150.

t2_01edge
Input{"startTime":[],"endTime":[],"profit":[]}
Expected0

Empty input means no jobs to schedule, so max profit is 0.

t2_02edge
Input{"startTime":[1],"endTime":[2],"profit":[100]}
Expected100

Single job can be scheduled alone for profit 100.

t2_03edge
Input{"startTime":[1,1,1],"endTime":[2,2,2],"profit":[10,50,20]}
Expected50

All jobs overlap completely; pick job with max profit 50.

t3_01corner
Input{"startTime":[1,2,3,4],"endTime":[3,5,10,6],"profit":[20,20,100,70]}
Expected120

Greedy approach picking jobs by earliest end time fails; correct is jobs 1 and 4 for total 90 or jobs 3 alone for 100, but best is jobs 1 and 4 for 90 or job 3 alone 100; actually jobs 1 and 4 overlap? Jobs 1(1-3), 4(4-6) no overlap, total 90; job 3 alone 100; best is 100.

t3_02corner
Input{"startTime":[1,2,3,4,5],"endTime":[2,3,4,5,6],"profit":[10,20,30,40,50]}
Expected90

Jobs are continuous intervals; only one job can be chosen at a time; best is jobs 1,3,5 for total 10+30+50=90.

t3_03corner
Input{"startTime":[5,1,3,4,2],"endTime":[6,2,5,7,4],"profit":[50,20,100,70,60]}
Expected150

Jobs unsorted by end time; algorithm must sort and still find max profit 150 by scheduling jobs 2,4,5.

t4_01performance
Input{"_description":"n=100000 at constraint boundary - executor generates this input"}
⏱ Performance - must finish in 2000ms

Test with 100000 jobs to verify O(n log n) DP with binary search completes within 2 seconds.

Practice

(1/5)
1. Consider the following Python code for counting the number of ways to make change. What is the output when calling change(5, [1, 2, 5])?
easy
A. 5
B. 3
C. 4
D. 6

Solution

  1. Step 1: Initialize dp array

    dp = [1,0,0,0,0,0] since dp[0]=1.
  2. Step 2: Update dp for each coin

    For coin=1: dp becomes [1,1,1,1,1,1]; for coin=2: dp updates to [1,1,2,2,3,3]; for coin=5: dp updates to [1,1,2,2,3,4].
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    dp[5] = 4 matches known output [OK]
Hint: Trace dp updates coin by coin [OK]
Common Mistakes:
  • Off-by-one in dp indexing
  • Confusing permutations with combinations
2. You need to find the minimum number of coins required to make up a given amount from an unlimited supply of given coin denominations. Which algorithmic approach guarantees an optimal solution for this problem?
easy
A. Greedy algorithm that picks the largest coin first until the amount is reached
B. Dynamic programming using a 1D array to store minimum coins needed for all amounts up to the target
C. Pure brute force recursion trying all combinations without memoization
D. Sorting coins and using binary search to find the best coin for each sub-amount

Solution

  1. Step 1: Understand problem constraints

    The problem requires minimum coins for any amount with unlimited coin usage, which fits unbounded knapsack pattern.
  2. Step 2: Identify algorithm that guarantees optimality

    Greedy fails for some coin sets; brute force is correct but inefficient; DP with 1D array efficiently computes minimum coins for all sub-amounts ensuring optimality.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    DP solves overlapping subproblems optimally [OK]
Hint: Unbounded knapsack DP ensures optimal minimum coins [OK]
Common Mistakes:
  • Assuming greedy always works
  • Ignoring memoization for efficiency
3. Consider the following code snippet implementing the minimum cost for tickets problem. What is the value of dp[0] after the loop completes for the input days = [1,4,6] and costs = [2,7,15]?
easy
A. 6
B. 7
C. 9
D. 4

Solution

  1. Step 1: Trace dp from the end

    dp[3] = 0 (base case). For i=2 (day=6): - 1-day ticket: cost=2 + dp[3]=2 - 7-day ticket: cost=7 + dp[3]=7 - 30-day ticket: cost=15 + dp[3]=15 Minimum = 2 -> dp[2]=2
  2. Step 2: Calculate dp[1] and dp[0]

    i=1 (day=4): - 1-day: cost=2 + dp[2]=4 - 7-day: cost=7 + dp[3]=7 - 30-day: cost=15 + dp[3]=15 Minimum=4 -> dp[1]=4 i=0 (day=1): - 1-day: cost=2 + dp[1]=6 - 7-day: cost=7 + dp[3]=7 - 30-day: cost=15 + dp[3]=15 Minimum=6 -> dp[0]=6
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    dp[0] correctly computed as 6 [OK]
Hint: Trace dp from end to start carefully [OK]
Common Mistakes:
  • Off-by-one in dp indexing
  • Miscomputing next_i with bisect
  • Confusing costs and dp sums
4. The following code attempts to solve the Partition to K Equal Sum Subsets problem using DP with bitmask tabulation. Identify the line containing the subtle bug that can cause incorrect results or infinite loops.
def canPartitionKSubsets(nums, k):
    total = sum(nums)
    if total % k != 0:
        return False
    target = total // k
    n = len(nums)
    nums.sort()
    if nums[-1] > target:
        return False

    dp = [-1] * (1 << n)
    dp[0] = 0

    for mask in range(1 << n):
        if dp[mask] == -1:
            continue
        for i in range(n):
            if (mask & (1 << i)) == 0 and dp[mask] + nums[i] <= target:
                next_mask = mask | (1 << i)
                dp[next_mask] = (dp[mask] + nums[i]) % target

    return dp[(1 << n) - 1] == 0
medium
A. Line: dp = [-1] * (1 << n)
B. Line: if dp[next_mask] == -1: (missing in this code)
C. Line: nums.sort()
D. Line: dp[next_mask] = (dp[mask] + nums[i]) % target

Solution

  1. Step 1: Identify missing condition

    The code lacks a check if dp[next_mask] is already set, so it overwrites states, causing incorrect results.
  2. Step 2: Pinpoint the buggy line

    The line assigning dp[next_mask] unconditionally overwrites previous valid states, breaking memoization.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Adding "if dp[next_mask] == -1:" before assignment fixes bug [OK]
Hint: Always check if dp state is unset before assignment [OK]
Common Mistakes:
  • Overwriting dp states without checking leads to incorrect answers
  • Not pruning symmetric states increases runtime
5. Suppose now you want to count the number of ways to make change but coins can be used at most once each (no reuse). Which modification to the DP approach correctly solves this variant?
hard
A. Use 1D DP iterating amounts forwards but reset dp array after each coin
B. Use 2D DP with dp[i][w] representing ways using first i coins for amount w, iterating amounts forwards
C. Use the same 1D DP but iterate amounts backwards from amount down to coin value
D. Use greedy approach picking largest coins first until amount is reached

Solution

  1. Step 1: Understand no reuse constraint

    Each coin can be used once, so combinations must not count repeated usage of the same coin.
  2. Step 2: Modify DP iteration order

    Iterating amounts backwards in 1D DP ensures each coin contributes only once per amount, preventing reuse.
  3. Step 3: Confirm correctness

    This approach correctly counts combinations without reuse, unlike forward iteration which allows multiple usage.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Backward iteration in 1D DP enforces single usage per coin [OK]
Hint: Backward iteration prevents coin reuse in 1D DP [OK]
Common Mistakes:
  • Using forward iteration allows unlimited reuse
  • Resetting dp array loses accumulated counts
  • Greedy approach misses many combinations