Largest Values From Labels
MedPrompt
You are given n item's value and label as two integer arrays values and labels. You are also given two integers numWanted and useLimit.
Your task is to find a subset of items with the maximum sum of their values such that:
- The number of items is at most
numWanted. - The number of items with the same label is at most
useLimit.
Return the maximum sum.
Example 1:
Input: values = [5,4,3,2,1], labels = [1,1,2,2,3], numWanted = 3, useLimit = 1
Output: 9
Explanation:
The subset chosen is the first, third, and fifth items with the sum of values 5 + 3 + 1.
Example 2:
Input: values = [5,4,3,2,1], labels = [1,3,3,3,2], numWanted = 3, useLimit = 2
Output: 12
Explanation:
The subset chosen is the first, second, and third items with the sum of values 5 + 4 + 3.
Example 3:
Input: values = [9,8,8,7,6], labels = [0,0,0,1,1], numWanted = 3, useLimit = 1
Output: 16
Explanation:
The subset chosen is the first and fourth items with the sum of values 9 + 7.
Constraints:
n == values.length == labels.length1 <= n <= 2 * 1040 <= values[i], labels[i] <= 2 * 1041 <= numWanted, useLimit <= n
Approaches
3 approaches with complexity analysis and trade-offs.
This approach explores every possible subset of items. For each subset, it checks if it adheres to the given constraints: the total number of items is no more than numWanted, and the count for each label does not exceed useLimit. If a subset is valid, its total value is calculated. The algorithm maintains a variable to store the maximum sum found across all valid subsets.
Algorithm
- Use a recursive or iterative method to generate all
2^nsubsets of thenitems. - For each generated subset:
- Verify its validity:
- Check if the number of items in the subset is at most
numWanted. - Use a hash map to count the occurrences of each label in the subset. Check if any label count exceeds
useLimit.
- Check if the number of items in the subset is at most
- If the subset is valid, calculate the sum of its values.
- Update the overall maximum sum if the current subset's sum is greater.
- Verify its validity:
- Return the overall maximum sum after checking all subsets.
Walkthrough
The brute-force method systematically generates every single combination of items. For each combination, it performs two checks: first, whether the size of the subset is within the numWanted limit, and second, whether the usage of any single label is within the useLimit. This is done by counting the items and their labels for each subset. If a subset is deemed valid, its total value is computed and compared against the maximum sum found so far, updating it if necessary. While this method is guaranteed to find the optimal solution, its exponential nature makes it impractical for anything but very small inputs.
// This is a conceptual illustration. A full implementation would be too complex and inefficient.class Solution { int maxSum = 0; public int largestValsFromLabels(int[] values, int[] labels, int numWanted, int useLimit) { // A recursive helper function would generate all subsets. // findSubsets(0, new ArrayList<>(), values, labels, numWanted, useLimit); // return maxSum; // Due to the 2^n complexity, this approach is not feasible and is omitted. return -1; // Placeholder for an infeasible solution } // void findSubsets(int index, List<Integer> currentSubsetIndices, ...) { // // Base case: if index reaches the end // if (index == values.length) { // // Check if current subset is valid // // If valid, calculate sum and update maxSum // return; // } // // Recurse without including the current item // findSubsets(index + 1, currentSubsetIndices, ...); // // Recurse including the current item // currentSubsetIndices.add(index); // findSubsets(index + 1, currentSubsetIndices, ...); // currentSubsetIndices.remove(currentSubsetIndices.size() - 1); // }}Complexity
Time
O(2^n * n). There are `2^n` possible subsets. For each subset, we iterate through its elements (up to `n` in length) to check validity and calculate the sum. This is computationally prohibitive for the given constraints.
Space
O(n). The space is needed for the recursion stack and to store the current subset being processed, along with a hash map for label counts.
Trade-offs
Pros
Guaranteed to find the correct answer by exploring all possibilities.
Cons
Extremely slow and will result in a 'Time Limit Exceeded' error for the given constraints.
The problem asks for the maximum sum, which suggests a greedy strategy. The most intuitive greedy choice is to always pick the available item with the highest value. To implement this, we can pair up the values and labels, sort them in descending order based on the values, and then iterate through them. We pick an item if it doesn't violate the constraints on the total number of items (numWanted) and the per-label usage (useLimit).
Algorithm
- Create a list of custom objects or pairs, where each object holds an item's value and its label.
- Sort this list of items in descending order based on their values.
- Initialize
maxSum = 0,itemsChosen = 0, and a hash maplabelCountsto track the usage of each label. - Iterate through the sorted list of items.
- For each item, if adding it does not violate the
numWantedanduseLimitconstraints, add its value tomaxSum, incrementitemsChosen, and update its label's count in the map. - Stop early if
itemsChosenreachesnumWanted. - Return
maxSum.
Walkthrough
This approach relies on the greedy principle that to maximize a sum, one should always prioritize adding the largest available numbers.
First, we combine the values and labels arrays into a single structure, like a list of Item objects, to keep them paired. Then, we sort these items based on their value in descending order. This ensures that we process items from most valuable to least valuable.
We then iterate through this sorted list. For each item, we check if we can select it. A selection is valid if we haven't yet reached our quota of numWanted items and if the number of items already chosen with the same label is less than useLimit. We use a hash map to keep track of the counts of each label selected so far. If the item can be selected, we add its value to our total sum and update our counts. We continue this process until we have either considered all items or have selected numWanted items.
class Item { int value; int label; Item(int value, int label) { this.value = value; this.label = label; }} class Solution { public int largestValsFromLabels(int[] values, int[] labels, int numWanted, int useLimit) { int n = values.length; List<Item> items = new ArrayList<>(); for (int i = 0; i < n; i++) { items.add(new Item(values[i], labels[i])); } // Sort items by value in descending order items.sort((a, b) -> b.value - a.value); Map<Integer, Integer> labelCounts = new HashMap<>(); int maxSum = 0; int itemsChosen = 0; for (Item item : items) { if (itemsChosen == numWanted) { break; } int currentLabelCount = labelCounts.getOrDefault(item.label, 0); if (currentLabelCount < useLimit) { maxSum += item.value; itemsChosen++; labelCounts.put(item.label, currentLabelCount + 1); } } return maxSum; }}Complexity
Time
O(n log n). Creating the list of items takes `O(n)`. Sorting the list dominates the complexity, taking `O(n log n)`. The final iteration through the sorted list takes `O(n)`.
Space
O(n). We need `O(n)` space to store the list of `Item` objects. The hash map for label counts can also store up to `n` unique labels in the worst case, taking `O(n)` space.
Trade-offs
Pros
Much more efficient than brute force and guaranteed to be correct for this problem.
Relatively simple to understand and implement.
Cons
The O(n log n) sorting step is slightly less efficient than a linear-time solution.
This approach is an optimization of the standard greedy method. Instead of a general-purpose comparison sort (O(n log n)), we can use a non-comparison sorting technique like counting sort or bucketing. This is possible because the item values are non-negative integers within a known, limited range. This allows us to effectively sort the items and process them in descending order of value in linear time.
Algorithm
- Determine the maximum possible value
maxValfrom the constraints. - Create an array of lists (buckets) of size
maxVal + 1. - Populate the buckets: for each item
(values[i], labels[i]), addlabels[i]to the list atbuckets[values[i]]. - Initialize
maxSum = 0,itemsChosen = 0, and a hash maplabelCounts. - Iterate downwards from
v = maxVal - 1to0. - For each value
v, iterate through the labels inbuckets[v]. - For each label, check if adding the item is valid (respects
numWantedanduseLimit). - If valid, add
vtomaxSum, incrementitemsChosen, and update the label count. - Stop if
itemsChosenreachesnumWanted. - Return
maxSum.
Walkthrough
The core of this optimized greedy approach is to avoid a comparison-based sort. Since item values are bounded, we can use an array as a set of 'buckets' to group items by value. The index of the array represents a value, and the element at that index is a list of labels of all items having that value.
- We create an array of lists,
buckets, where the size is determined by the maximum possible value (20001 according to constraints). - We iterate through the input items once, placing each item's label into the corresponding value's bucket. For example, an item with
value=50, label=1results in1being added tobuckets[50]. - After populating the buckets, we iterate through the
bucketsarray from the highest index (highest value) down to zero. This naturally processes items in descending order of value. - For each value, we look at the labels in its bucket. For each label, we apply the same greedy logic as before: if we have room for more items (
itemsChosen < numWanted) and haven't exhausted the limit for that label (labelCounts.get(...) < useLimit), we select the item. - This continues until we have selected
numWanteditems or have checked all buckets.
This method achieves a linear time complexity because it avoids the O(n log n) sorting step.
class Solution { public int largestValsFromLabels(int[] values, int[] labels, int numWanted, int useLimit) { // The maximum value is constrained to 20000 int maxVal = 20001; List<Integer>[] buckets = new ArrayList[maxVal]; for (int i = 0; i < maxVal; i++) { buckets[i] = new ArrayList<>(); } for (int i = 0; i < values.length; i++) { buckets[values[i]].add(labels[i]); } Map<Integer, Integer> labelCounts = new HashMap<>(); int maxSum = 0; int itemsChosen = 0; for (int v = maxVal - 1; v >= 0; v--) { if (itemsChosen == numWanted) { break; } if (buckets[v].isEmpty()) { continue; } for (int label : buckets[v]) { if (itemsChosen == numWanted) { break; } int currentLabelCount = labelCounts.getOrDefault(label, 0); if (currentLabelCount < useLimit) { maxSum += v; itemsChosen++; labelCounts.put(label, currentLabelCount + 1); } } } return maxSum; }}Complexity
Time
O(n + V_max). Populating the buckets takes `O(n)`. Iterating through the buckets takes `O(V_max)` for the outer loop, and the inner loops over all buckets will execute a total of `n` times across all iterations. Thus, the total time is linear.
Space
O(n + V_max), where `n` is the number of items and `V_max` is the maximum possible value. The `buckets` array requires `O(V_max)` for the array itself plus `O(n)` to store all the labels. The `labelCounts` map can take up to `O(n)` space.
Trade-offs
Pros
Achieves optimal linear time complexity.
Very efficient for the given problem constraints.
Cons
Uses more space than the comparison-sort approach if the maximum value
V_maxis significantly larger thann. For this problem's constraints, they are of similar magnitude.
Solutions
Solution
class Solution {public int largestValsFromLabels(int[] values, int[] labels, int numWanted, int useLimit) { int n = values.length; int[][] pairs = new int[n][2]; for (int i = 0; i < n; ++i) { pairs[i] = new int[]{values[i], labels[i]}; } Arrays.sort(pairs, (a, b)->b[0] - a[0]); Map<Integer, Integer> cnt = new HashMap<>(); int ans = 0, num = 0; for (int i = 0; i < n && num < numWanted; ++i) { int v = pairs[i][0], l = pairs[i][1]; if (cnt.getOrDefault(l, 0) < useLimit) { cnt.merge(l, 1, Integer : : sum); num += 1; ans += v; } } return ans; }}class Solution {public: int largestValsFromLabels(vector<int> &values, vector<int> &labels, int numWanted, int useLimit) { int n = values.size(); vector<pair<int, int>> pairs(n); for (int i = 0; i < n; ++i) { pairs[i] = {-values[i], labels[i]}; } sort(pairs.begin(), pairs.end()); unordered_map<int, int> cnt; int ans = 0, num = 0; for (int i = 0; i < n && num < numWanted; ++i) { int v = -pairs[i].first, l = pairs[i].second; if (cnt[l] < useLimit) { ++cnt[l]; ++num; ans += v; } } return ans; }};class Solution: def largestValsFromLabels(self, values: List[int], labels: List[int], numWanted: int, useLimit: int) -> int: ans = num = 0 cnt = Counter() for v, l in sorted(zip(values, labels), reverse=True): if cnt[l] < useLimit: cnt[l] += 1 num += 1 ans += v if num == numWanted: break return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.