1090. Largest Values From Labels
Problem Description
You have n
items, each with a value and a label. These are given as two arrays: values
and labels
, where values[i]
is the value of the i-th item and labels[i]
is its label.
You need to select a subset of these items to maximize the total value, but with two constraints:
- Total item limit: You can select at most
numWanted
items in total. - Per-label limit: For items with the same label, you can select at most
useLimit
of them.
The goal is to find the maximum possible sum of values you can achieve while respecting both constraints.
For example, if you have items with values [5, 4, 3, 2, 1]
and labels [1, 1, 2, 2, 3]
, with numWanted = 3
and useLimit = 1
, you would:
- Pick the item with value 5 and label 1 (used 1 item with label 1)
- Pick the item with value 3 and label 2 (used 1 item with label 2)
- Pick the item with value 1 and label 3 (used 1 item with label 3)
- Total sum = 5 + 3 + 1 = 9
Note that you couldn't pick the item with value 4 (label 1) because you already used 1 item with label 1, reaching the useLimit
.
The solution uses a greedy approach: sort items by value in descending order, then greedily select items with the highest values while tracking how many items of each label have been used. The process stops when either numWanted
items have been selected or no more valid items can be added.
Intuition
When we want to maximize the sum of values with constraints, a greedy approach often works well. The key insight is that we should prioritize items with higher values first, regardless of their labels.
Think of it like filling a basket with the most valuable items from a store, but with rules about how many of each type you can take. Naturally, you'd want to pick the most expensive items first, then work your way down to less valuable ones.
Why does this greedy strategy work here? Because:
-
Value is independent of label: An item's value doesn't depend on its label, so we can consider all items together and sort them by value alone.
-
No penalty for label diversity: There's no requirement to use items from different labels - we just have a limit on how many items with the same label we can use. This means if the top 3 most valuable items all have different labels and we can take 3 items total, we should take all of them.
-
Constraints are only limiting, not directing: The constraints (
numWanted
anduseLimit
) only restrict what we can take, they don't force us to take certain items. This makes the greedy approach optimal - we never need to "save space" for a less valuable item.
The strategy becomes clear: sort all items by value in descending order, then iterate through them, taking each item if:
- We haven't reached the total limit of
numWanted
items - We haven't used
useLimit
items with this particular label yet
We use a counter to track how many items of each label we've used. This greedy approach guarantees the maximum sum because we always pick the next most valuable item that we're allowed to take.
Solution Approach
The implementation follows the greedy strategy we identified:
-
Combine and Sort: First, we pair each value with its corresponding label using
zip(values, labels)
, then sort these pairs in descending order by value usingsorted(..., reverse=True)
. This ensures we process items from highest value to lowest. -
Initialize Tracking Variables:
ans
: Accumulates the sum of selected valuesnum
: Counts how many items we've selected so farcnt = Counter()
: A dictionary-like structure that tracks how many items of each label we've used
-
Greedy Selection: We iterate through the sorted (value, label) pairs:
for v, l in sorted(zip(values, labels), reverse=True):
For each item, we check if we can add it:
if cnt[l] < useLimit
: Check if we haven't exceeded the limit for this label
If we can add the item:
cnt[l] += 1
: Increment the count for this labelnum += 1
: Increment the total number of items selectedans += v
: Add the value to our running sum
-
Early Termination:
if num == numWanted: break
Once we've selected
numWanted
items, we stop immediately since we've reached the maximum number of items allowed. -
Return Result: The function returns
ans
, which contains the maximum sum achievable.
The time complexity is O(n log n)
due to sorting, where n
is the number of items. The space complexity is O(n)
for storing the sorted pairs and the counter, which in the worst case could have an entry for each unique label.
This approach is optimal because at each step, we make the locally best choice (picking the highest available value), which leads to the globally optimal solution.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the greedy solution works.
Input:
values = [9, 8, 8, 7, 6]
labels = [0, 0, 0, 1, 1]
numWanted = 3
useLimit = 2
Step 1: Combine and Sort First, we pair values with labels and sort by value (descending):
[(9, 0), (8, 0), (8, 0), (7, 1), (6, 1)]
Step 2: Initialize Variables
ans = 0
(running sum)num = 0
(items selected)cnt = Counter()
(empty counter for label usage)
Step 3: Iterate and Select Items
Iteration 1: (9, 0)
- Check:
cnt[0] < useLimit
โ0 < 2
โ - Select this item!
- Update:
cnt[0] = 1
,num = 1
,ans = 9
Iteration 2: (8, 0)
- Check:
cnt[0] < useLimit
โ1 < 2
โ - Select this item!
- Update:
cnt[0] = 2
,num = 2
,ans = 17
Iteration 3: (8, 0)
- Check:
cnt[0] < useLimit
โ2 < 2
โ - Cannot select (already used 2 items with label 0)
- Skip this item
Iteration 4: (7, 1)
- Check:
cnt[1] < useLimit
โ0 < 2
โ - Select this item!
- Update:
cnt[1] = 1
,num = 3
,ans = 24
- Check:
num == numWanted
โ3 == 3
โ - Stop! We've reached the item limit
Result: Maximum sum = 24
We selected items with values [9, 8, 7], using 2 items with label 0 and 1 item with label 1, respecting both the total limit (3 items) and per-label limit (at most 2 items per label).
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def largestValsFromLabels(
6 self, values: List[int], labels: List[int], numWanted: int, useLimit: int
7 ) -> int:
8 # Initialize result sum and count of selected items
9 total_sum = 0
10 items_selected = 0
11
12 # Counter to track how many times each label has been used
13 label_usage_count = Counter()
14
15 # Create pairs of (value, label) and sort by value in descending order
16 # This greedy approach ensures we pick the highest values first
17 value_label_pairs = sorted(zip(values, labels), reverse=True)
18
19 # Iterate through sorted pairs from highest to lowest value
20 for value, label in value_label_pairs:
21 # Check if we can still use this label (haven't reached useLimit)
22 if label_usage_count[label] < useLimit:
23 # Use this item: increment label count and update results
24 label_usage_count[label] += 1
25 items_selected += 1
26 total_sum += value
27
28 # Stop if we've selected the desired number of items
29 if items_selected == numWanted:
30 break
31
32 return total_sum
33
1class Solution {
2 public int largestValsFromLabels(int[] values, int[] labels, int numWanted, int useLimit) {
3 int n = values.length;
4
5 // Create pairs of [value, label] for sorting
6 int[][] valueLabelPairs = new int[n][2];
7 for (int i = 0; i < n; i++) {
8 valueLabelPairs[i] = new int[] {values[i], labels[i]};
9 }
10
11 // Sort pairs in descending order by value (greedy approach - pick highest values first)
12 Arrays.sort(valueLabelPairs, (a, b) -> b[0] - a[0]);
13
14 // Track how many times each label has been used
15 Map<Integer, Integer> labelUsageCount = new HashMap<>();
16
17 int totalSum = 0;
18 int itemsSelected = 0;
19
20 // Iterate through sorted pairs and select values greedily
21 for (int i = 0; i < n && itemsSelected < numWanted; i++) {
22 int currentValue = valueLabelPairs[i][0];
23 int currentLabel = valueLabelPairs[i][1];
24
25 // Check if we can still use this label (haven't reached useLimit)
26 if (labelUsageCount.getOrDefault(currentLabel, 0) < useLimit) {
27 // Update label usage count
28 labelUsageCount.merge(currentLabel, 1, Integer::sum);
29
30 // Add value to result and increment selected items counter
31 itemsSelected++;
32 totalSum += currentValue;
33 }
34 }
35
36 return totalSum;
37 }
38}
39
1class Solution {
2public:
3 int largestValsFromLabels(vector<int>& values, vector<int>& labels, int numWanted, int useLimit) {
4 int n = values.size();
5
6 // Create pairs of (value, label) for sorting
7 // Using negative values to sort in descending order
8 vector<pair<int, int>> valueLabelPairs(n);
9 for (int i = 0; i < n; ++i) {
10 valueLabelPairs[i] = {-values[i], labels[i]};
11 }
12
13 // Sort pairs by value in descending order (due to negative values)
14 sort(valueLabelPairs.begin(), valueLabelPairs.end());
15
16 // Track how many times each label has been used
17 unordered_map<int, int> labelUsageCount;
18
19 int totalSum = 0;
20 int itemsSelected = 0;
21
22 // Greedily select items with largest values
23 for (int i = 0; i < n && itemsSelected < numWanted; ++i) {
24 // Extract original value (negate to get positive) and label
25 int currentValue = -valueLabelPairs[i].first;
26 int currentLabel = valueLabelPairs[i].second;
27
28 // Check if we can still use this label
29 if (labelUsageCount[currentLabel] < useLimit) {
30 // Use this item
31 ++labelUsageCount[currentLabel];
32 ++itemsSelected;
33 totalSum += currentValue;
34 }
35 }
36
37 return totalSum;
38 }
39};
40
1/**
2 * Finds the largest sum by selecting items with the highest values
3 * while respecting label usage limits
4 *
5 * @param values - Array of item values
6 * @param labels - Array of item labels (corresponding to values)
7 * @param numWanted - Maximum number of items to select
8 * @param useLimit - Maximum number of items with the same label that can be selected
9 * @returns The maximum sum of selected values
10 */
11function largestValsFromLabels(
12 values: number[],
13 labels: number[],
14 numWanted: number,
15 useLimit: number,
16): number {
17 const itemCount: number = values.length;
18
19 // Create pairs of [value, label] for sorting
20 const valueLabelPairs: number[][] = new Array(itemCount);
21 for (let i = 0; i < itemCount; i++) {
22 valueLabelPairs[i] = [values[i], labels[i]];
23 }
24
25 // Sort pairs by value in descending order (highest values first)
26 valueLabelPairs.sort((a: number[], b: number[]) => b[0] - a[0]);
27
28 // Track how many times each label has been used
29 const labelUsageCount: Map<number, number> = new Map<number, number>();
30
31 let totalSum: number = 0;
32 let selectedCount: number = 0;
33
34 // Iterate through sorted pairs and select items greedily
35 for (let i = 0; i < itemCount && selectedCount < numWanted; i++) {
36 const [currentValue, currentLabel] = valueLabelPairs[i];
37 const currentLabelUsage: number = labelUsageCount.get(currentLabel) || 0;
38
39 // Check if we can use this label (haven't exceeded useLimit)
40 if (currentLabelUsage < useLimit) {
41 // Update label usage count
42 labelUsageCount.set(currentLabel, currentLabelUsage + 1);
43 // Increment selected items counter
44 selectedCount++;
45 // Add value to total sum
46 totalSum += currentValue;
47 }
48 }
49
50 return totalSum;
51}
52
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of the values/labels arrays.
- The
zip(values, labels)
operation takesO(n)
time to create pairs - The
sorted()
function performs a comparison-based sort on these pairs, which takesO(n log n)
time - The for loop iterates through the sorted pairs at most
n
times (or untilnumWanted
items are selected), performingO(1)
operations in each iteration:- Checking
cnt[l] < useLimit
:O(1)
average case for dictionary lookup - Updating
cnt[l]
:O(1)
average case for dictionary update - Adding to
ans
andnum
:O(1)
- Checking
if num == numWanted
:O(1)
- Checking
The dominant operation is the sorting step, making the overall time complexity O(n log n)
.
Space Complexity: O(n)
where n
is the length of the input arrays.
- The
zip(values, labels)
creates a new iterable of pairs:O(n)
space - The
sorted()
function creates a new sorted list:O(n)
space - The
Counter()
dictionarycnt
stores at mostO(k)
entries wherek
is the number of unique labels, and in the worst casek = n
:O(n)
space - Other variables (
ans
,num
) use constant space:O(1)
The total space complexity is O(n)
due to the sorted list and the counter dictionary.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Not Understanding the Per-Label Limit
A common misconception is thinking that useLimit
applies globally across all labels combined, rather than to each label individually. For example, with useLimit = 2
, some might incorrectly think you can only use 2 items total regardless of their labels.
Incorrect Understanding:
# Wrong: Treating useLimit as a global constraint total_items_used = 0 for value, label in sorted_pairs: if total_items_used < useLimit: # WRONG! total_items_used += 1 # ...
Correct Understanding:
# Right: useLimit applies per label label_usage_count = Counter() for value, label in sorted_pairs: if label_usage_count[label] < useLimit: # Correct - check per label label_usage_count[label] += 1 # ...
Pitfall 2: Forgetting Early Termination
Without the early termination check, the code might continue processing items even after selecting numWanted
items, leading to incorrect results where too many items are selected.
Problematic Code:
for value, label in sorted_pairs: if label_usage_count[label] < useLimit: label_usage_count[label] += 1 items_selected += 1 total_sum += value # Missing break condition! return total_sum # Might have selected more than numWanted items
Solution: Always include the termination check immediately after selecting an item:
if items_selected == numWanted: break
Pitfall 3: Incorrect Sorting Order
Sorting in ascending order instead of descending would select the smallest values first, completely defeating the greedy strategy.
Wrong:
# This would pick smallest values first
value_label_pairs = sorted(zip(values, labels)) # Missing reverse=True
Correct:
# Pick largest values first for maximum sum
value_label_pairs = sorted(zip(values, labels), reverse=True)
Pitfall 4: Edge Case Handling
The code handles edge cases naturally, but it's important to understand them:
- When
numWanted
exceeds the total number of items available - When
useLimit
is very large (larger than any label frequency) - When all items have the same label
The current implementation handles these correctly because:
- The loop naturally terminates when all items are processed
- The Counter returns 0 for unseen labels, preventing KeyError
- The break condition prevents selecting more than
numWanted
items
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!