題意
給定一個長度為 n n n 的二維整數陣列 i t e m s items i t e m s 和一個整數 k k k 。
i t e m s [ i ] = [ p r o f i t i , c a t e g o r y i ] items[i] = [profit_i, category_i] i t e m s [ i ] = [ p ro f i t i , c a t e g or y i ] ,其中 p r o f i t i profit_i p ro f i t i 和 c a t e g o r y i category_i c a t e g or y i 分別表示第 i i i 個項目的利潤和類別。
我們定義項目 子序列(Subsequence) 的 優雅度(Elegance) 為 total_profit + distinct_categories 2 \text{total\_profit} + \text{distinct\_categories}^2 total_profit + distinct_categories 2 ,其中 total_profit \text{total\_profit} total_profit 是子序列中所有利潤的總和,distinct_categories \text{distinct\_categories} distinct_categories 是選定子序列中不同類別的數量。
你的任務是找出 i t e m s items i t e m s 中所有大小為 k k k 的子序列中的最大優雅度,以整數表示形式返回。
注意:一個陣列的 子序列(Subsequence) 是由刪除一些元素(可能沒有)而生成的新陣列,而不改變其餘元素的相對順序。
思路:反悔貪心
基於 貪心(Greedy) 的思想,可以使用一種稱為「反悔貪心」的策略來解決這個問題。
我們可以先不考慮不同類別的數量,只考慮利潤的總和,故此時利潤最大的 k k k 個物品之利潤總和即為答案。故可以將物品按照 利潤由大到小 的順序排序。然後從前 k k k 個物品開始,計算它們的總利潤與不同類別數的平方之和,作為初始 的最大優雅度。
接下來,我們從第 k + 1 k+1 k + 1 個物品開始遍歷,對於每個物品:
若這個物品所屬的種類已經出現過,那麼顯然不會增加優雅度,可以直接跳過這個物品。
否則,我們可以考慮是否替換掉已經選定的物品中的某個物品,以嘗試讓總優雅度變大。
若先前的物品中,沒有重複種類的物品,則替換後 total_profit \text{total\_profit} total_profit 會變小,但 distinct_categories \text{distinct\_categories} distinct_categories 不變,因此顯然也不會增加優雅度,可以直接跳過這個物品。
若先前的物品中,有重複種類的物品,則我們可以選擇重複的物品中利潤最小的那個,用這個新物品來替換它。替換後雖然 total_profit \text{total\_profit} total_profit 會變小,但 distinct_categories \text{distinct\_categories} distinct_categories 會增加,因此 可能 會增加優雅度。也就是 反悔 之前的選擇,重新選擇一個更好的子序列。
為此,我們可以維護一個集合 s e e n seen see n 來記錄已經出現過的種類,以及一個陣列 d u l p l i c a t e dulplicate d u lpl i c a t e 來紀錄重複出現的種類中,第二個以後的物品的利潤。在每次遍歷時,我們可以根據這兩個資料結構來判斷是否需要替換掉某個物品。由於對於 d u l p l i c a t e dulplicate d u lpl i c a t e 的添加和刪除操作都是在陣列的尾部進行的,因此也可以使用堆疊(Stack)來實現。
複雜度分析
時間複雜度:O ( n log n ) \mathcal{O}(n \log n) O ( n log n ) ,排序需要 O ( n log n ) \mathcal{O}(n \log n) O ( n log n ) 時間,遍歷和操作每個物品需要 O ( n ) \mathcal{O}(n) O ( n ) 時間,因此總體時間複雜度為 O ( n log n ) \mathcal{O}(n \log n) O ( n log n ) 。
空間複雜度:O ( n ) \mathcal{O}(n) O ( n ) ,使用了 s e e n seen see n 集合來記錄已經出現過的種類,以及 d u l p l i c a t e dulplicate d u lpl i c a t e 陣列來存儲相同種類的利潤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution : def findMaximumElegance (self, items: List [List [int ]], k: int ) -> int : n = len (items) items.sort(key= lambda x : x[0 ], reverse=True ) cur = 0 seen = set () dulplicate = [] for i in range (min (k, n)): profit, category = items[i] cur += profit if category in seen: dulplicate.append(profit) else : seen.add(category) ans = cur + len (seen) * len (seen) for i in range (k, n): profit, category = items[i] if dulplicate and category not in seen: seen.add(category) cur += profit - dulplicate.pop() ans = max (ans, cur + len (seen) * len (seen)) return ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution {public : long long findMaximumElegance (vector<vector<int >>& items, int k) { int n = items.size (); sort (items.begin (), items.end (), greater<vector<int >>()); long long ans = 0 , cur = 0 , sz = 0 ; unordered_set<int > seen; vector<int > dulplicate; for (int i = 0 ; i < min (k, n); i++) { int profit = items[i][0 ], category = items[i][1 ]; cur += profit; if (seen.count (category)) dulplicate.push_back (profit); else seen.insert (category); } sz = seen.size (); ans = cur + sz * sz; for (int i = k; i < n; i++) { int profit = items[i][0 ], category = items[i][1 ]; if (!dulplicate.empty () && !seen.count (category)) { seen.insert (category); cur += profit - dulplicate.back (); dulplicate.pop_back (); } sz = seen.size (); ans = max (ans, cur + sz * sz); } return ans; } };
寫在最後
Cover photo is generated by @たろたろ , thanks for their work!