3645. Maximum Total from Optimal Activation Order
Problem Description
You have two arrays value
and limit
, both of length n
. Each element starts as inactive and can be activated in any order you choose.
The activation process follows these rules:
-
Activation Condition: To activate element at index
i
, the number of currently active elements must be strictly less thanlimit[i]
. For example, iflimit[i] = 3
, you can only activate elementi
when there are 0, 1, or 2 active elements. -
Value Addition: When you activate element at index
i
, you addvalue[i]
to your total score. -
Deactivation Rule: After each activation, if the number of active elements becomes
x
, then all elementsj
wherelimit[j] β€ x
become permanently inactive. This happens even if those elements were already active - they still become permanently unusable.
The goal is to find the maximum total value you can achieve by choosing the optimal activation order.
Example walkthrough:
- If you have
value = [2, 3, 4]
andlimit = [2, 1, 3]
- Element at index 1 has
limit[1] = 1
, so it can only be activated when 0 elements are active - Element at index 0 has
limit[0] = 2
, so it can be activated when 0 or 1 elements are active - Element at index 2 has
limit[2] = 3
, so it can be activated when 0, 1, or 2 elements are active - After activating any element, check if any elements should be permanently deactivated based on the current count of active elements
The solution groups elements by their limit values and greedily selects the highest-value elements from each group, taking at most lim
elements from each group with limit lim
(since after activating lim
elements, all elements with that limit become permanently inactive).
Intuition
The key insight is understanding what happens when we activate elements. Once we have x
active elements, all elements with limit[j] β€ x
become permanently inactive. This means elements with the same limit value form a critical threshold - once we reach that many active elements, they all become unusable.
Think about elements with limit = k
. We can activate them as long as we have fewer than k
active elements. But once we reach k
active elements total, all elements with limit = k
become permanently inactive. This creates a natural grouping: elements with the same limit value compete with each other because activating k
of them means none of the remaining ones in that group can ever be used.
Since elements within the same limit group compete for activation slots, and we can activate at most k
elements before all elements with limit = k
become inactive, we should greedily choose the most valuable ones from each group.
The strategy becomes clear:
- Group all elements by their
limit
value - For each group with
limit = k
, we can activate at mostk
elements before they all become inactive - To maximize our total, we should pick the
k
most valuable elements from this group (or all of them if there are fewer thank
)
This greedy approach works because the groups don't interfere with each other's selection - we're simply maximizing the value we extract from each limit threshold before it becomes unreachable. By sorting each group and taking the top values, we ensure we're getting the maximum possible contribution from each limit level.
The beauty of this solution is that it transforms a complex activation ordering problem into a simple selection problem: for each limit value lim
, select up to lim
elements with the highest values.
Learn more about Greedy, Two Pointers, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows the greedy strategy we identified:
-
Group elements by limit value: We use a
defaultdict(list)
to create a dictionaryg
where each key is a limit value and the corresponding value is a list of all element values that have that limit.g = defaultdict(list) for v, lim in zip(value, limit): g[lim].append(v)
This groups elements efficiently - for example, if we have
value = [2, 5, 3, 4]
andlimit = [2, 3, 2, 3]
, we getg = {2: [2, 3], 3: [5, 4]}
. -
Sort and select top values from each group: For each limit group, we sort the values in ascending order and then select the last
lim
elements (which are the largest).for lim, vs in g.items(): vs.sort() ans += sum(vs[-lim:])
The key insight here is using Python's negative indexing:
vs[-lim:]
gives us the lastlim
elements from the sorted list, which are thelim
largest values.- If a group has more than
lim
elements, we take exactly thelim
largest ones - If a group has fewer than
lim
elements, the slicevs[-lim:]
will just return all elements in the group
- If a group has more than
-
Calculate the total: We accumulate the sum of selected values across all groups to get our maximum possible total.
Time Complexity: O(n log n)
where n
is the length of the input arrays. The dominant operation is sorting the values within each group.
Space Complexity: O(n)
for storing the grouped elements in the dictionary.
The elegance of this solution lies in recognizing that the complex activation rules simplify to a straightforward selection problem when elements are grouped by their limit values.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with value = [6, 1, 5, 8]
and limit = [2, 3, 2, 3]
.
Step 1: Group elements by limit value
- Elements with
limit = 2
: indices 0 and 2, with values [6, 5] - Elements with
limit = 3
: indices 1 and 3, with values [1, 8]
This gives us: g = {2: [6, 5], 3: [1, 8]}
Step 2: Process each limit group
For limit = 2
group with values [6, 5]:
- We can activate at most 2 elements before all elements with
limit = 2
become permanently inactive - Sort the values: [5, 6]
- Take the last 2 elements (the 2 largest): [5, 6]
- Contribution to total: 5 + 6 = 11
For limit = 3
group with values [1, 8]:
- We can activate at most 3 elements before all elements with
limit = 3
become permanently inactive - Sort the values: [1, 8]
- Take the last 3 elements, but we only have 2, so take all: [1, 8]
- Contribution to total: 1 + 8 = 9
Step 3: Calculate final result Total maximum value = 11 + 9 = 20
Why this works:
- When we activate 2 elements total, all elements with
limit β€ 2
become permanently inactive - When we activate 3 elements total, all elements with
limit β€ 3
become permanently inactive - By taking the most valuable elements from each group before they become inactive, we maximize our total score
- The actual activation order doesn't matter as long as we pick the right elements from each group
Solution Implementation
1from typing import List
2from collections import defaultdict
3
4class Solution:
5 def maxTotal(self, value: List[int], limit: List[int]) -> int:
6 # Group values by their corresponding limit
7 # Key: limit value, Value: list of values with that limit
8 limit_to_values = defaultdict(list)
9
10 # Populate the dictionary by pairing each value with its limit
11 for val, lim in zip(value, limit):
12 limit_to_values[lim].append(val)
13
14 # Initialize the total sum
15 total_sum = 0
16
17 # Process each group of values
18 for limit_value, value_list in limit_to_values.items():
19 # Sort values in ascending order
20 value_list.sort()
21
22 # Take the largest 'limit_value' number of elements from this group
23 # If there are fewer elements than the limit, take all of them
24 # [-limit_value:] gets the last 'limit_value' elements (the largest ones)
25 total_sum += sum(value_list[-limit_value:])
26
27 return total_sum
28
1class Solution {
2 public long maxTotal(int[] value, int[] limit) {
3 // Create a map to group values by their corresponding limits
4 // Key: limit value, Value: list of values with that limit
5 Map<Integer, List<Integer>> limitToValuesMap = new HashMap<>();
6
7 // Group values by their limits
8 for (int i = 0; i < value.length; i++) {
9 limitToValuesMap.computeIfAbsent(limit[i], k -> new ArrayList<>())
10 .add(value[i]);
11 }
12
13 // Initialize the maximum total sum
14 long maxSum = 0;
15
16 // Process each group of values with the same limit
17 for (Map.Entry<Integer, List<Integer>> entry : limitToValuesMap.entrySet()) {
18 int currentLimit = entry.getKey();
19 List<Integer> valuesList = entry.getValue();
20
21 // Sort values in descending order to prioritize larger values
22 valuesList.sort((a, b) -> b - a);
23
24 // Select up to 'currentLimit' items from the sorted list
25 // Cannot exceed the actual size of the list
26 int itemsToSelect = Math.min(currentLimit, valuesList.size());
27
28 // Add the top values to the total sum
29 for (int i = 0; i < itemsToSelect; i++) {
30 maxSum += valuesList.get(i);
31 }
32 }
33
34 return maxSum;
35 }
36}
37
1class Solution {
2public:
3 long long maxTotal(vector<int>& value, vector<int>& limit) {
4 // Group values by their corresponding limits using a hash map
5 // Key: limit value, Value: list of values with that limit
6 unordered_map<int, vector<int>> limitToValuesMap;
7 int n = value.size();
8
9 // Build the mapping of limits to their corresponding values
10 for (int i = 0; i < n; ++i) {
11 limitToValuesMap[limit[i]].push_back(value[i]);
12 }
13
14 // Calculate the maximum total by selecting top values for each limit group
15 long long totalSum = 0;
16
17 // Iterate through each limit group
18 for (auto& [currentLimit, valuesInGroup] : limitToValuesMap) {
19 // Sort values in descending order to prioritize higher values
20 sort(valuesInGroup.begin(), valuesInGroup.end(), greater<int>());
21
22 // Select up to 'currentLimit' items from this group
23 // Cannot exceed the number of available items in the group
24 int itemsToSelect = min(currentLimit, static_cast<int>(valuesInGroup.size()));
25
26 // Add the top 'itemsToSelect' values to the total sum
27 for (int i = 0; i < itemsToSelect; ++i) {
28 totalSum += valuesInGroup[i];
29 }
30 }
31
32 return totalSum;
33 }
34};
35
1/**
2 * Calculates the maximum total value by selecting items with respect to their limits.
3 * Each item has a value and a limit indicating the maximum number of items that can be selected
4 * from the group with the same limit.
5 *
6 * @param value - Array of item values
7 * @param limit - Array of limits corresponding to each item
8 * @returns The maximum total value that can be obtained
9 */
10function maxTotal(value: number[], limit: number[]): number {
11 // Group items by their limit value
12 // Map structure: limit -> array of values with that limit
13 const limitToValuesMap = new Map<number, number[]>();
14
15 // Iterate through all items and group them by their limits
16 for (let i = 0; i < value.length; i++) {
17 // Initialize array for new limit if not exists
18 if (!limitToValuesMap.has(limit[i])) {
19 limitToValuesMap.set(limit[i], []);
20 }
21 // Add current value to the corresponding limit group
22 limitToValuesMap.get(limit[i])!.push(value[i]);
23 }
24
25 // Calculate the maximum total value
26 let maxTotalValue = 0;
27
28 // Process each limit group
29 for (const [currentLimit, valuesInGroup] of limitToValuesMap) {
30 // Sort values in descending order to prioritize higher values
31 valuesInGroup.sort((a, b) => b - a);
32
33 // Select up to 'currentLimit' items from this group (the highest values)
34 // and add their sum to the total
35 const selectedValues = valuesInGroup.slice(0, currentLimit);
36 const sumOfSelectedValues = selectedValues.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
37 maxTotalValue += sumOfSelectedValues;
38 }
39
40 return maxTotalValue;
41}
42
Time and Space Complexity
Time Complexity: O(n + k * m log m)
- Creating the dictionary and grouping values by limit takes
O(n)
time, wheren
is the length of the input arrays - Iterating through each unique limit group takes
O(k)
iterations, wherek
is the number of unique limits - For each group with
m
values:- Sorting takes
O(m log m)
time - Slicing and summing the last
lim
elements takesO(min(lim, m))
time
- Sorting takes
- In the worst case where all values have the same limit, we have one group with
n
values, giving usO(n log n)
- In the best case where all values have different limits, we have
n
groups each with 1 value, giving usO(n)
- The overall time complexity is
O(n + k * m log m)
wherem
is the average group size, which simplifies toO(n log n)
in the worst case
Space Complexity: O(n)
- The defaultdict
g
stores alln
values from the input, requiringO(n)
space - The sorted lists are created in-place modifications of the lists already in the dictionary
- The slicing operation
vs[-lim:]
creates a temporary list of at mostlim
elements, but this doesn't exceedO(n)
in total - Overall space complexity is
O(n)
for storing all input values in the dictionary structure
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Deactivation Rule
A critical pitfall is misinterpreting when elements become permanently inactive. Many might think that elements with limit[j] β€ x
become inactive only if they haven't been activated yet. However, the rule states that all such elements become permanently inactive, even those already active. This means they can't contribute to future activations or be activated if not already active.
Wrong interpretation: "Only unactivated elements with limit[j] β€ x
become unavailable"
Correct interpretation: "All elements with limit[j] β€ x
become permanently inactive, regardless of their current state"
2. Incorrect Greedy Selection Within Groups
When selecting elements from each group, a common mistake is to take the first lim
elements instead of the largest lim
elements. This happens when forgetting to sort or when using incorrect slice notation.
Incorrect approach:
# Taking first lim elements (wrong!)
total_sum += sum(value_list[:limit_value])
Correct approach:
# Sort first, then take the last (largest) lim elements
value_list.sort()
total_sum += sum(value_list[-limit_value:])
3. Off-by-One Error in Understanding Activation Conditions
The activation condition states that element i
can be activated when the number of active elements is strictly less than limit[i]
. A common mistake is using "less than or equal to" instead.
Wrong: "Can activate when active count β€ limit[i]"
Correct: "Can activate when active count < limit[i]"
This means if limit[i] = 3
, you can activate when there are 0, 1, or 2 active elements, but NOT when there are already 3 active elements.
4. Not Handling Edge Cases with Python Slicing
While Python's negative slicing [-lim:]
handles cases where the list has fewer than lim
elements gracefully (it returns all elements), developers coming from other languages might worry about this and add unnecessary checks:
Overcomplicated (unnecessary):
if len(value_list) >= limit_value:
total_sum += sum(value_list[-limit_value:])
else:
total_sum += sum(value_list)
Simple and correct:
total_sum += sum(value_list[-limit_value:])
Python's slicing automatically handles the case where the slice extends beyond the list boundaries.
Which two pointer techniques do you use to check if a string is a palindrome?
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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
Want a Structured Path to Master System Design Too? Donβt Miss This!