💡 Skipping job 4 yields higher profit than taking it.
reconstruct
Final result read from dp array
The maximum profit is the last value in dp array, representing best schedule profit.
💡 The dp array accumulates max profit; last cell is final answer.
Line:return dp[-1]
💡 dp[-1] = 150 is the maximum profit achievable.
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]) # STEP 1
n = len(jobs) # STEP 2
ends = [job[1] for job in jobs] # STEP 2
dp = [0] * n # STEP 2
for i in range(n): # STEP 3-9
start_i, end_i, profit_i = jobs[i] # STEP i
idx = bisect.bisect_right(ends, start_i - 1) - 1 # STEP i
take = profit_i + (dp[idx] if idx >= 0 else 0) # STEP i
skip = dp[i - 1] if i > 0 else 0 # STEP i
dp[i] = max(take, skip) # STEP i
return dp[-1] # STEP 10
if __name__ == '__main__':
print(jobScheduling([1,2,3,4,6], [3,5,10,6,9], [20,20,100,70,60])) # Expected 150
📊
Maximum Profit in Job Scheduling - Watch the Algorithm Execute, Step by Step
Watching each step reveals how the algorithm balances taking or skipping jobs based on compatibility and profit, making the DP logic clear without needing to read code.
Step 1/10
·Active fill★Answer cell
Initializing dp array
i\w
0
1
2
3
4
i=0
?
?
?
?
?
Job 1 (1,3,20)
Initializing dp array
i\w
0
1
2
3
4
i=0
?
?
?
?
?
dp unfilled
Item 0 - wt:3 val:20
i\w
0
1
2
3
4
i=0
20
?
?
?
?
dp[0] = 20 (take job 0)
Item 1 - wt:5 val:20
i\w
0
1
2
3
4
i=0
20
20
?
?
?
dp[1] = 20 (skip or take job 1)
Item 2 - wt:6 val:70
i\w
0
1
2
3
4
i=0
20
20
90
?
?
dp[2] = 90 (take job 2 + dp[0])
Item 3 - wt:9 val:60
i\w
0
1
2
3
4
i=0
20
20
90
?
?
Compatible job idx = 2
Item 3 - wt:9 val:60
i\w
0
1
2
3
4
i=0
20
20
90
150
?
dp[3] = 150 (take job 3 + dp[2])
Item 4 - wt:10 val:100
i\w
0
1
2
3
4
i=0
20
20
90
150
?
Compatible job idx = 0
Item 4 - wt:10 val:100
i\w
0
1
2
3
4
i=0
20
20
90
150
150
dp[4] = 150 (skip job 4)
Item 4 - wt:10 val:100
i\w
0
1
2
3
4
i=0
20
20
90
150
150
Answer: 150
Key Takeaways
✓ Sorting jobs by end time is essential for efficient compatibility checks.
Without sorting, finding compatible jobs would be inefficient and complex.
✓ The dp array stores the maximum profit achievable up to each job, reflecting cumulative decisions.
This shows how dynamic programming builds solutions incrementally.
✓ Binary search quickly finds the last compatible job, enabling O(n log n) complexity.
This optimization is key to making the algorithm efficient and practical.
Practice
(1/5)
1. 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]?
2. Given the following code, what is the output when coins = [1, 2, 5] and amount = 5?
def count_ways_space_opt(coins, amount):
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins:
for w in range(coin, amount + 1):
dp[w] += dp[w - coin]
return dp[amount]
coins = [1, 2, 5]
amount = 5
print(count_ways_space_opt(coins, amount))
easy
A. 4
B. 7
C. 5
D. 6
Solution
Step 1: Trace dp array updates for each coin
Start dp = [1,0,0,0,0,0]. After coin=1, dp = [1,1,1,1,1,1]. After coin=2, dp = [1,1,2,2,3,3]. After coin=5, dp = [1,1,2,2,3,6].
Step 2: Confirm dp[5] value
dp[5] = 6, representing 6 ways to make amount 5 with coins [1,2,5].
Final Answer:
Option D -> Option D
Quick Check:
Manual dp tracing matches output 6 [OK]
Hint: DP accumulates counts incrementally per coin [OK]
Common Mistakes:
Off-by-one in dp indexing
Miscounting after last coin iteration
Confusing permutations with combinations
3. Consider the following buggy code for lastStoneWeightII. Which line contains the subtle bug that causes incorrect results on some inputs?
medium
A. Line 4: dp[0] = true is missing
B. Line 7: Outer loop over stones
C. Line 8: Inner loop iterates backwards
D. Line 10: Return statement calculation
Solution
Step 1: Identify dp initialization
dp[0] must be true to represent sum zero achievable with no stones; missing this means no sums are marked achievable.
Step 2: Consequence of missing dp[0] = true
Without dp[0] = true, dp array remains false, so no sums can be formed, causing the function to fail to find any valid partition.
Final Answer:
Option A -> Option A
Quick Check:
Initializing dp[0] is critical base case for subset sum DP [OK]
Hint: dp[0] = true is base case for subset sums [OK]
Common Mistakes:
Forgetting dp[0] initialization
Iterating forward in inner loop causing double counting
Miscomputing return value
4. Suppose the problem is modified so that each string can be chosen multiple times (unbounded usage). Which change to the dynamic programming approach correctly adapts the solution?
hard
A. Iterate dp array forwards to allow multiple counts of the same string
B. Iterate dp array backwards as before to avoid counting duplicates
C. Use recursion without memoization to handle repeated choices
D. Increase dp dimensions to track usage count of each string
Solution
Step 1: Understand difference between 0/1 and unbounded knapsack
Unbounded knapsack allows multiple uses of the same item, so dp updates must allow reuse within the same iteration.
Step 2: Adjust dp iteration order
Iterating dp forwards enables using updated states multiple times, correctly counting repeated strings.
Step 3: Confirm correctness
Backward iteration prevents reuse in the same iteration, which is incorrect for unbounded usage.
5. Suppose the problem is modified so that each element in the array can be used multiple times (unlimited reuse) to form k equal sum subsets. Which of the following modifications to the DP with bitmask tabulation approach correctly adapts to this change?
hard
A. Use backtracking with memoization on subsets but do not use bitmasking since reuse breaks subset uniqueness
B. Keep the same bitmask DP but allow setting dp[next_mask] multiple times without restriction
C. Replace bitmask DP with a classic unbounded knapsack DP that tracks sums without bitmasking
D. Modify the bitmask DP to reset dp states after each full subset sum is reached to allow reuse
Solution
Step 1: Understand reuse impact
Allowing unlimited reuse breaks the uniqueness assumption of subsets represented by bitmasks.