2813. Maximum Elegance of a K-Length Subsequence
Problem Description
This problem provides us with a 2D integer array named items
that represents a collection of items, each with a profit and category. Our objective is to find a subsequence (a sequence that can be derived from another sequence by deleting some elements without changing the order of the remaining elements) of these items with exactly k
elements such that the "elegance" of this subsequence is maximized.
The "elegance" is a measure defined specifically for a subsequence of items. It's calculated as the sum of all profits (total_profit
) of items in the subsequence plus the square of the number of distinct categories (distinct_categories^2
) present in the subsequence.
To summarize, we are tasked to:
- Select
k
items fromitems
as a subsequence. - Calculate the elegance of the subsequence.
- Maximize the elegance across all possible subsequences of size
k
.
Intuition
The solution to this problem involves a greedy approach. The key insight is that higher profit items are likely to contribute more to the elegance score since the sum of profits is a linear component in the elegance score calculation. On the other hand, the number of distinct categories contributes quadratically to the elegance score. Thus, we should aim to maximize profits while also considering the diversity of categories to a reasonable extent.
To arrive at the solution, we start by sorting all items by profit in descending order because we want to initially consider the items with the highest profits. After sorting, we:
- Choose the first
k
items, assuming it gives us the maximum total profit. - Use a set to keep track of the distinct categories we have included so far.
- Use a separate list to keep track of profits from duplicate categories (since selecting these doesn't increase the number of distinct categories).
Once we do the initial calculation of elegance with the first k
items, we then consider each item beyond the first k
. We have two scenarios:
- If the category is already included, we skip the item because including another from the same category won't increase the distinct category count.
- If the category isn't included, and we have a previous item from a duplicate category, we can potentially increase the elegance by swapping out a lower profit, duplicate item, with a higher profit, new category item.
By updating the total profit and distinct category count accordingly and evaluating the elegance each time we consider an item, we can determine the maximum possible elegance we can achieve considering the subsequence size restriction.
Finally, the answer (ans
) is the highest elegance we can get from all the subsequences considered through this process.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implemented solution leverages a greedy algorithm to optimize for maximum elegance. The algorithm and the data structures used can be broken down as follows:
-
Sorting: We sort the items in descending order by their profits using
items.sort(key=lambda x: -x[0])
. Sorting is crucial because it allows us to examine the items with the highest profits first, thus aligning with our greedy approach. -
Data Structures:
- A set
vis
(short for "visited") to keep track of the categories already included in our subsequence. A set is chosen for its property of storing unique elements, which efficiently handles the requirement for distinct categories. - A list
dup
is used to maintain the profits of items from categories we have already chosen. This list functions as a stack, where we add profits of duplicate category items as we encounter them among the firstk
items.
- A set
-
Initial Calculation:
- We iterate through the first
k
items and calculate an initial total profittot
by summing up their profits and populatingvis
anddup
with the categories and profits of duplicate items, respectively. - We also keep track of an
ans
as the current maximum elegance.
- We iterate through the first
-
Iterative Optimization:
- We continue to iterate beyond the first
k
items. For each item, if its category is already invis
, we ignore it, as explained in the intuition part. - If an item's category is not in
vis
and there are items indup
, we have an opportunity to possibly increase our elegance score:- We add the new category to
vis
. - We update the total profit
tot
by replacing one of the duplicate category items with the current one. This is done by subtracting the profit of the duplicate item (the last element indup
, becausedup
operates as a stack) and adding the profit of the current item.
- We add the new category to
- We continue to iterate beyond the first
-
Elegance Calculation:
- After considering each new item and potential swaps, we calculate the current elegance as
tot + len(vis) ** 2
and check whether it is greater than our previously recorded maximum. If it is, we updateans
with this value.
- After considering each new item and potential swaps, we calculate the current elegance as
-
Return Value:
- The maximum found elegance
ans
is returned as the result, representing the maximum elegance from all subsequences of sizek
.
- The maximum found elegance
This approach takes advantage of the greedy method for an initial selection of the most promising items and then iterates over the remaining ones with the potential to yield higher elegance by introducing more diversity in categories. It systematically evaluates each item's impact on total profit and the distinct category count to arrive at the optimal elegance score.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach with a small example. Suppose we have the following items
with the first value representing the profit and the second the category:
items = [[5, 1], [4, 2], [7, 1], [3, 3], [8, 2]] k = 3
We aim to maximize the elegance of a subsequence of k=3
items from this list. Here's a step-by-step walkthrough implementing the solution approach:
-
Sort items by profit in descending order:
We apply sorting to the items to consider the highest profits first.
Sorted
items
:[[8, 2], [7, 1], [5, 1], [4, 2], [3, 3]]
-
Initialization using data structures:
We create a set
vis
to track distinct categories, and a listdup
to store profits of duplicate category items.vis
(set): empty.dup
(list): empty. -
Initial Calculation of elegance:
We iterate through the first
k
items, add their profits totot
, and populatevis
anddup
accordingly.For the first
k=3
items:- item
[8, 2]
:tot = 8, vis = {2}, dup = []
- item
[7, 1]
:tot = 15 (8+7), vis = {2, 1}, dup = []
- item
[5, 1]
:tot = 20 (15+5), vis = {2, 1}, dup = [5]
(since category 1 is a duplicate)
Initial
ans
(maximum elegance):20 + 2^2 = 24
- item
-
Iterative Optimization:
Now we consider items beyond the first
k=3
.- item
[4, 2]
: skipped, because category2
is already included invis
. - item
[3, 3]
: since category3
is not invis
and we have a lower profit duplicate[5, 1]
indup
, we can swap them:- Add category
3
tovis
:vis = {1, 2, 3}
- Swap
[5, 1]
with[3, 3]
:tot
becomes18 (20 - 5 + 3)
- Recalculate elegance:
18 + 3^2 = 27
, which is greater than the previousans
- Update
ans
:ans = 27
- Add category
- item
-
Return Value:
After evaluating all items, we have our final
ans
value.The maximum elegance
ans
is27
, which is the answer to our problem.
In this example, we can see that even though the last item has a lower profit than some of the ones we ignored, its unique category increased the elegance score more significantly than a higher profit item from an already existing category would have. This exemplifies the importance of maximizing the sum of profits while also increasing the number of distinct categories, showcasing the effectiveness of the greedy approach in this scenario.
Solution Implementation
1from typing import List
2
3class Solution:
4 def findMaximumElegance(self, items: List[List[int]], k: int) -> int:
5 # Sort the items by price in descending order
6 items.sort(key=lambda x: -x[0])
7
8 total_price = 0
9 seen_colors = set()
10 duplicate_prices = []
11
12 # Process the first K items and calculate their total price
13 # Record if a color has been seen before and keep track of duplicates
14 for price, color in items[:k]:
15 total_price += price
16 if color not in seen_colors:
17 seen_colors.add(color)
18 else:
19 duplicate_prices.append(price)
20
21 # Calculate the current elegance as the sum of prices and the square of
22 # unique colors count
23 max_elegance = total_price + len(seen_colors) ** 2
24
25 # Process the rest of the items beyond the initial K
26 for price, color in items[k:]:
27 # If the color is already seen or there are no duplicates, continue
28 if color in seen_colors or not duplicate_prices:
29 continue
30
31 # Add the new color to the seen set
32 seen_colors.add(color)
33
34 # Update the total price by swapping the lowest duplicate and the new item
35 total_price += price - duplicate_prices.pop()
36
37 # Recalculate the elegance and update max_elegance if it's greater
38 max_elegance = max(max_elegance, total_price + len(seen_colors) ** 2)
39
40 # Return the maximum elegance found
41 return max_elegance
42
1class Solution {
2 public long findMaximumElegance(int[][] items, int k) {
3 // Sort the items based on their price in descending order
4 Arrays.sort(items, (a, b) -> b[0] - a[0]);
5
6 // Total number of items
7 int itemCount = items.length;
8
9 // Total elegance score
10 long totalElegance = 0;
11
12 // Set to record which colors have been used
13 Set<Integer> usedColors = new HashSet<>();
14
15 // A deque to store the prices of duplicate colored items
16 Deque<Integer> duplicatePrices = new ArrayDeque<>();
17
18 // Pick the first 'k' items
19 for (int i = 0; i < k; ++i) {
20 int price = items[i][0], color = items[i][1];
21 totalElegance += price;
22
23 // If the color is already seen, add the price to the deque of duplicates
24 if (!usedColors.add(color)) {
25 duplicatePrices.push(price);
26 }
27 }
28
29 // Calculate the maximum elegance for the first 'k' items
30 long maxElegance = totalElegance + (long) usedColors.size() * usedColors.size();
31
32 // Now try to replace duplicate items with items of unused colors to maximize elegance
33 for (int i = k; i < itemCount; ++i) {
34 int price = items[i][0], color = items[i][1];
35
36 // Skip if the color is already used and no duplicates to replace
37 if (usedColors.contains(color) || duplicatePrices.isEmpty()) {
38 continue;
39 }
40
41 // Add this unused color and update the total elegance
42 usedColors.add(color);
43 totalElegance += price - duplicatePrices.pop();
44
45 // Calculate the new maximum elegance with this configuration
46 maxElegance = Math.max(maxElegance, totalElegance + (long) usedColors.size() * usedColors.size());
47 }
48
49 // Return the maximum elegance that can be achieved
50 return maxElegance;
51 }
52}
53
1#include <vector>
2#include <unordered_set>
3#include <stack>
4#include <algorithm>
5
6class Solution {
7public:
8 long long findMaximumElegance(std::vector<std::vector<int>>& items, int k) {
9 // Sort the items in decreasing order based on price (first element of each pair)
10 std::sort(items.begin(), items.end(), [](const std::vector<int>& a, const std::vector<int>& b) {
11 return a[0] > b[0];
12 });
13
14 long long totalElegance = 0; // This will keep track of the total elegance score
15 std::unordered_set<int> uniqueColors; // Set to store unique colors
16 std::stack<int> duplicatePrices; // Stack to store prices of duplicate color items
17
18 // Process the first 'k' items as they contribute to elegance score
19 for (int i = 0; i < k; ++i) {
20 int price = items[i][0], color = items[i][1];
21 totalElegance += price; // Add item price to the total elegance score
22 if (uniqueColors.count(color)) {
23 // If the color is a duplicate, push its price onto the stack
24 duplicatePrices.push(price);
25 } else {
26 // If the color is unique, insert it into the set
27 uniqueColors.insert(color);
28 }
29 }
30
31 int itemCount = items.size(); // Total number of items
32 // Calculate initial elegance score including the bonus from unique colors
33 long long maxElegance = totalElegance + static_cast<long long>(uniqueColors.size() * uniqueColors.size());
34
35 // Process the remaining items to potentially replace duplicates with unique colors
36 for (int i = k; i < itemCount; ++i) {
37 int price = items[i][0], color = items[i][1];
38 // If current color is already used or there are no duplicates, continue
39 if (uniqueColors.count(color) || duplicatePrices.empty()) {
40 continue;
41 }
42 // Insert unique color and update total elegance score by replacing a duplicate
43 uniqueColors.insert(color);
44 totalElegance += price - duplicatePrices.top();
45 duplicatePrices.pop();
46 // Check if the current configuration yields a higher elegance score and update maxElegance if so
47 maxElegance = std::max(maxElegance, totalElegance + static_cast<long long>(uniqueColors.size() * uniqueColors.size()));
48 }
49
50 return maxElegance; // Return the maximum elegance score found
51 }
52};
53
1function findMaximumElegance(items: number[][], k: number): number {
2 // Sort items in decreasing order of price
3 items.sort((a, b) => b[0] - a[0]);
4
5 // Initialize the total score
6 let totalScore = 0;
7
8 // Set to keep track of unique items
9 const visitedCategories: Set<number> = new Set();
10
11 // Array to keep track of duplicates in the top 'k' items
12 const duplicatePrices: number[] = [];
13
14 // Loop through the top 'k' items
15 for (const [price, category] of items.slice(0, k)) {
16 totalScore += price;
17 if (visitedCategories.has(category)) {
18 // If category is already visited, add the price to duplicates
19 duplicatePrices.push(price);
20 } else {
21 // Else add the category to visited
22 visitedCategories.add(category);
23 }
24 }
25
26 // Calculate initial elegance score
27 let maxElegance = totalScore + visitedCategories.size ** 2;
28
29 // Loop through the rest of the items beyond the top 'k'
30 for (const [price, category] of items.slice(k)) {
31 // Continue if category is already visited or there are no duplicates
32 if (visitedCategories.has(category) || duplicatePrices.length === 0) {
33 continue;
34 }
35 // Replace the lowest duplicate price with the current price and update the score
36 totalScore += price - duplicatePrices.pop()!;
37 // Add the new category
38 visitedCategories.add(category);
39 // Update the maximum elegance score if it's larger
40 maxElegance = Math.max(maxElegance, totalScore + visitedCategories.size ** 2);
41 }
42 // Return the maximum elegance score
43 return maxElegance;
44}
45
Time and Space Complexity
Time Complexity
The time complexity of the code is primarily determined by the sorting operation and the subsequent for-loops.
-
The
sort()
function on the list of items,items.sort(key=lambda x: -x[0])
, has a time complexity ofO(n log n)
, wheren
is the number of items. This comes from the fact that Python's Timsort algorithm is used for sorting, which has this time complexity for sorting a list. -
The first for-loop,
for p, c in items[:k]:
, iterates over the firstk
elements, thus takingO(k)
time. -
The second for-loop,
for p, c in items[k:]:
, in the worst case, could iterate over 'n - k' elements ifdup
is non-empty until the end of the list. This will have a time complexity ofO(n - k)
.
Since k <= n
, we have O(k)
and O(n - k)
both being bounded by O(n)
. However, the sort operation dominates the time complexity, so we can simplify and say the overall time complexity of this code is O(n log n)
.
Space Complexity
The space complexity of the code is determined by the storage used for the vis
set, the dup
list, and the list slice used for iterating items.
-
The
vis
set can potentially store a unique color for every item, in the worst case, which would requireO(n)
space. -
The
dup
list can store at mostk
prices, in the case where all items among the topk
have duplicate colors. So in the worst case, it would needO(k)
space. -
List slicing, such as
items[:k]
anditems[k:]
, in Python does not create a shallow copy, hence it doesn't add to the space complexity.
Therefore, the dominating factor here is the space required for the vis
set, which is O(n)
. The dup
list, even though it may grow, is always within the limit set by vis
, so the space complexity remains O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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 algomonster s3 us east 2 amazonaws com 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
Want a Structured Path to Master System Design Too? Don’t Miss This!