3767. Maximize Points After Choosing K Tasks
Problem Description
You are given two integer arrays, technique1 and technique2, each of length n, where n represents the number of tasks you need to complete.
For each task i, you have two ways to complete it:
- If you complete the
ith task using technique 1, you earntechnique1[i]points. - If you complete the
ith task using technique 2, you earntechnique2[i]points.
You are also given an integer k, which represents the minimum number of tasks that must be completed using technique 1.
The rules are:
- You must complete at least
ktasks using technique 1. These do not have to be the firstktasks — they can be anyktasks of your choosing. - The remaining tasks can be completed using either technique, whichever you prefer.
Your goal is to maximize the total number of points earned across all n tasks.
Return an integer representing the maximum total points you can earn.
How We Pick the Algorithm
Why Heap / Sortings?
This problem maps to Heap / Sortings through a short path in the full flowchart.
Selecting which tasks to switch to technique 1 to maximize points involves sorting gains and using a heap for the best k selections.
Open in FlowchartIntuition
Let's start by thinking about a simpler baseline. Suppose we ignore the constraint for a moment and just assign every task to technique 2. This gives us a starting total score of sum(technique2).
Now, the question becomes: which tasks should we switch from technique 2 to technique 1?
For each task i, switching it to technique 1 changes our score by technique1[i] - technique2[i]. Let's call this value the gain of task i (it can be positive, negative, or zero). If we switch task i, our total score changes by exactly this amount.
With this view, the problem splits into two parts:
-
The forced part: We are required to use technique 1 on at least
ktasks. Since switching a task affects the score by its gain, and we have no choice but to switchkof them, we should pick thektasks with the largest gains. This way, the unavoidable switches hurt us as little as possible (or help us as much as possible). To do this, we sort all tasks by their gaintechnique1[i] - technique2[i]in descending order and pick the topk. -
The optional part: For all remaining tasks, switching is purely optional. Here the rule is simple — switch a task to technique 1 only if it increases (or keeps) the score, meaning we switch when
technique1[i] >= technique2[i](i.e., the gain is non-negative). Switching a task with a negative gain would only lower our total, so we leave those on technique 2.
This greedy choice works because each task's decision is independent — switching one task never affects the gain of another. So picking the largest gains for the forced k and then greedily taking every remaining non-negative gain guarantees the maximum possible total.
Pattern Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
We use a Greedy + Sorting strategy. Let n be the number of tasks.
Step 1: Start with everything on technique 2.
We initialize our answer as the sum of all technique 2 values:
ans = sum(technique2)
This represents the score if no task uses technique 1 yet.
Step 2: Sort tasks by their gain in descending order.
For each task i, the gain of switching to technique 1 is technique1[i] - technique2[i]. We sort the task indices (not the values themselves) by this gain from largest to smallest:
idx = sorted(range(n), key=lambda i: -(technique1[i] - technique2[i]))
Here idx is an ordered list of task indices. The first elements of idx are the tasks that benefit the most (or lose the least) from switching to technique 1. Using indices lets us look up both technique1[i] and technique2[i] later.
Step 3: Handle the forced k tasks.
We must use technique 1 on at least k tasks. To minimize the cost (or maximize the benefit) of this requirement, we pick the first k entries of idx — the ones with the largest gains. For each, we remove its technique 2 contribution and add its technique 1 contribution:
for i in idx[:k]: ans -= technique2[i] ans += technique1[i]
This is equivalent to adding each task's gain to ans, and we do it for the k best gains.
Step 4: Handle the remaining optional tasks.
For every remaining task (idx[k:]), switching is optional. We only switch when it does not reduce the score, i.e., when technique1[i] >= technique2[i]:
for i in idx[k:]: if technique1[i] >= technique2[i]: ans -= technique2[i] ans += technique1[i]
Tasks with a negative gain are left on technique 2, so they keep their original contribution.
Step 5: Return the result.
After both loops, ans holds the maximum achievable score:
return ans
Complexity Analysis:
- Time complexity:
O(n log n), dominated by the sorting step. The two loops together run inO(n). - Space complexity:
O(n), for storing the sorted index listidx.
Example Walkthrough
Let's trace through the solution with a small, concrete example.
Input:
technique1 = [3, 1, 5, 2]technique2 = [2, 4, 1, 2]k = 2n = 4
This means we have 4 tasks, and we must use technique 1 on at least 2 of them.
Step 1: Start with everything on technique 2.
We assume every task uses technique 2 and sum those values:
ans = sum(technique2) = 2 + 4 + 1 + 2 = 9
So our baseline score is 9.
Step 2: Compute each task's gain and sort by gain (descending).
The gain of switching task i to technique 1 is technique1[i] - technique2[i]:
Task i | technique1[i] | technique2[i] | Gain |
|---|---|---|---|
| 0 | 3 | 2 | +1 |
| 1 | 1 | 4 | -3 |
| 2 | 5 | 1 | +4 |
| 3 | 2 | 2 | 0 |
Sorting the task indices by gain in descending order:
Gains: +4 (task 2), +1 (task 0), 0 (task 3), -3 (task 1) idx = [2, 0, 3, 1]
Step 3: Handle the forced k = 2 tasks.
We must switch at least 2 tasks, so we take the first 2 entries of idx → tasks 2 and 0 (the largest gains). For each, remove its technique 2 contribution and add its technique 1 contribution:
- Task 2:
ans = 9 - technique2[2] + technique1[2] = 9 - 1 + 5 = 13(gain +4) - Task 0:
ans = 13 - technique2[0] + technique1[0] = 13 - 2 + 3 = 14(gain +1)
After the forced switches, ans = 14.
Step 4: Handle the remaining optional tasks.
The remaining tasks are idx[2:] = [3, 1]. We switch each only if technique1[i] >= technique2[i]:
- Task 3:
technique1[3] = 2,technique2[3] = 2→2 >= 2is true, so switch:ans = 14 - 2 + 2 = 14(gain 0, no harm) - Task 1:
technique1[1] = 1,technique2[1] = 4→1 >= 4is false, so skip. Task 1 stays on technique 2.
After the optional switches, ans = 14.
Step 5: Return the result.
The maximum total points is:
ans = 14
Verification of the final assignment:
| Task | Technique used | Points |
|---|---|---|
| 0 | Technique 1 | 3 |
| 1 | Technique 2 | 4 |
| 2 | Technique 1 | 5 |
| 3 | Technique 1 (or 2) | 2 |
- Tasks using technique 1:
{0, 2, 3}→ 3 tasks, which satisfies the constraint of at leastk = 2. ✓ - Total points:
3 + 4 + 5 + 2 = 14. ✓
The greedy choice locked in the two highest-gain tasks for the forced requirement, then freely grabbed every remaining non-negative gain, leaving only the genuinely losing switch (task 1) untouched — yielding the optimal total of 14.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def maxPoints(self, technique1: List[int], technique2: List[int], k: int) -> int:
6 n = len(technique1)
7
8 # Sort indices by the gain (technique1 - technique2) in descending order.
9 # The largest gains come first, so the top k forced picks lose the least.
10 sorted_indices = sorted(
11 range(n),
12 key=lambda i: -(technique1[i] - technique2[i]),
13 )
14
15 # Baseline: assume we pick technique2 for every index.
16 total = sum(technique2)
17
18 # For the first k indices (highest gain), we are forced to use technique1.
19 # Swap out technique2 and swap in technique1 for each of them.
20 for i in sorted_indices[:k]:
21 total -= technique2[i]
22 total += technique1[i]
23
24 # For the remaining indices, switching is optional.
25 # Only switch to technique1 when it is at least as large as technique2.
26 for i in sorted_indices[k:]:
27 if technique1[i] >= technique2[i]:
28 total -= technique2[i]
29 total += technique1[i]
30
31 return total
321class Solution {
2 public long maxPoints(int[] technique1, int[] technique2, int k) {
3 int n = technique1.length;
4
5 // Create an index array so we can sort indices without disturbing
6 // the original input arrays.
7 Integer[] indices = new Integer[n];
8 Arrays.setAll(indices, i -> i);
9
10 // Sort indices in descending order by the "gain" of switching from
11 // technique2 to technique1, i.e. (technique1[i] - technique2[i]).
12 // The largest gains come first.
13 Arrays.sort(indices,
14 (a, b) -> (technique1[b] - technique2[b]) - (technique1[a] - technique2[a]));
15
16 // Start by assuming every element uses technique2.
17 long answer = 0;
18 for (int value : technique2) {
19 answer += value;
20 }
21
22 // For the first k indices (the ones with the largest gain) we are
23 // forced/allowed to switch to technique1, regardless of sign.
24 for (int i = 0; i < k; i++) {
25 int index = indices[i];
26 answer -= technique2[index];
27 answer += technique1[index];
28 }
29
30 // For the remaining indices, only switch to technique1 when it does
31 // not decrease the total (i.e. the gain is non-negative).
32 for (int i = k; i < n; i++) {
33 int index = indices[i];
34 if (technique1[index] >= technique2[index]) {
35 answer -= technique2[index];
36 answer += technique1[index];
37 }
38 }
39
40 return answer;
41 }
42}
431class Solution {
2public:
3 long long maxPoints(vector<int>& technique1, vector<int>& technique2, int k) {
4 int n = technique1.size();
5
6 // Create an index array [0, 1, 2, ..., n-1]
7 vector<int> indices(n);
8 iota(indices.begin(), indices.end(), 0);
9
10 // Sort indices by the gain (technique1[i] - technique2[i]) in descending order.
11 // The indices with the largest gain come first, so we can prioritize
12 // applying technique1 on them within the limit k.
13 sort(indices.begin(), indices.end(), [&](int i, int j) {
14 return (technique1[i] - technique2[i]) > (technique1[j] - technique2[j]);
15 });
16
17 // Start by assuming technique2 is applied to every element.
18 long long answer = 0;
19 for (int value : technique2) {
20 answer += value;
21 }
22
23 // For the first k elements (those with the highest gain), we are forced
24 // (or at least allowed) to switch from technique2 to technique1.
25 for (int i = 0; i < k; ++i) {
26 int index = indices[i];
27 answer -= technique2[index]; // Remove the technique2 contribution
28 answer += technique1[index]; // Add the technique1 contribution
29 }
30
31 // For the remaining elements, switch to technique1 only when it is
32 // beneficial (i.e., technique1 yields at least as much as technique2).
33 for (int i = k; i < n; ++i) {
34 int index = indices[i];
35 if (technique1[index] >= technique2[index]) {
36 answer -= technique2[index]; // Remove the technique2 contribution
37 answer += technique1[index]; // Add the technique1 contribution
38 }
39 }
40
41 return answer;
42 }
43};
441/**
2 * Calculates the maximum total points achievable.
3 *
4 * Each element can contribute either its value from `technique1` or `technique2`.
5 * We must use `technique1` for exactly at least `k` elements (the ones where the
6 * gain from switching to technique1 is largest). For the remaining elements,
7 * we freely pick whichever technique yields a higher value.
8 *
9 * @param technique1 - Point values when using the first technique.
10 * @param technique2 - Point values when using the second technique.
11 * @param k - The minimum number of elements that must use technique1.
12 * @returns The maximum total points obtainable.
13 */
14function maxPoints(technique1: number[], technique2: number[], k: number): number {
15 const length: number = technique1.length;
16
17 // Index array used to order elements by their "gain" of switching to technique1.
18 const indices: number[] = Array.from({ length }, (_, i) => i);
19
20 // Sort indices in descending order of (technique1 - technique2).
21 // The largest gains come first, so the first k indices are the best
22 // candidates to forcibly assign technique1.
23 indices.sort(
24 (i, j) =>
25 (technique1[j] - technique2[j]) - (technique1[i] - technique2[i]),
26 );
27
28 // Start by assuming every element uses technique2.
29 let answer: number = technique2.reduce((sum, value) => sum + value, 0);
30
31 // For the top k elements (largest gain), force the use of technique1
32 // regardless of whether it is beneficial.
33 for (let i = 0; i < k; i++) {
34 const index: number = indices[i];
35 answer -= technique2[index];
36 answer += technique1[index];
37 }
38
39 // For the remaining elements, switch to technique1 only when it gives
40 // a value greater than or equal to technique2.
41 for (let i = k; i < length; i++) {
42 const index: number = indices[i];
43 if (technique1[index] >= technique2[index]) {
44 answer -= technique2[index];
45 answer += technique1[index];
46 }
47 }
48
49 return answer;
50}
51Time and Space Complexity
-
Time Complexity:
O(n × log n), wherenis the length of the input arrays. The dominant operation issorted(range(n), key=...), which sorts thenindices and costsO(n × log n). The initialsum(technique2)takesO(n), and the two subsequent loops iterate over thekselected indices and the remainingn - kindices respectively, together contributingO(n). Therefore the overall time complexity isO(n × log n). -
Space Complexity:
O(n), wherenis the length of the input arrays. Theidxlist created bysorted(range(n), ...)storesnindices, requiringO(n)extra space. The sorting algorithm itself may also use up toO(n)auxiliary space. Hence the overall space complexity isO(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting that the k forced tasks may have negative gain
The most common mistake is treating Step 4's optional logic as if it applied everywhere. A naive solution might be written as:
# WRONG: only switches when it helps, ignoring the "at least k" constraint
total = sum(technique2)
for i in range(n):
if technique1[i] >= technique2[i]:
total -= technique2[i]
total += technique1[i]
return total
This silently ignores the requirement that at least k tasks must use technique 1. If fewer than k tasks have a non-negative gain, this code returns an invalid answer — it never forces the extra switches.
Why the correct solution avoids this: By sorting in descending order of gain and unconditionally switching the first k indices (even when their gain is negative), we guarantee the constraint is met while losing the least possible amount of points. The k smallest losses are exactly the tasks ranked just after the positive-gain ones.
Pitfall 2: Sorting the values instead of the indices
A tempting shortcut is to sort the gains directly:
# WRONG: loses the link to technique1[i] / technique2[i]
gains = sorted((technique1[i] - technique2[i] for i in range(n)), reverse=True)
After this you only have the difference, not the original pair, so you can no longer correctly decide which contribution to add. Worse, when you later need technique1[i] and technique2[i] separately for the optional phase, the mapping is gone.
Fix: Sort the indices by gain (as the solution does with sorted(range(n), ...)), or sort (gain, t1, t2) tuples together so each element keeps its full information.
Pitfall 3: Double-counting or off-by-one when handling the forced vs. optional split
A subtle bug is processing the same index in both loops, or slicing incorrectly:
for i in sorted_indices[:k]: # forced ... for i in sorted_indices[k - 1:]: # WRONG: re-processes index at position k-1 ...
This would apply two swaps to the boundary task, corrupting the total.
Fix: Use disjoint, contiguous slices — sorted_indices[:k] and sorted_indices[k:] — so every index is processed exactly once. Also validate the input so that 0 <= k <= n; an out-of-range k would silently misbehave with Python slicing (e.g., k > n would force fewer than expected without raising an error).
# Defensive guard
k = max(0, min(k, n))
This clamps k into a valid range and keeps the two-phase logic correct.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapHow would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!