有 n 块不同类型的奶酪,分别位于下标 0 到 n - 1。下标 i 处的奶酪被第一只老鼠吃掉的得分为 reward}_1[i],被第二只老鼠吃掉的得分为 reward}_2[i]。
如果 n 块奶酪都被第二只老鼠吃掉,则得分为数组 reward}_2 的元素之和,记为 sum。如果下标 i 处的奶酪被第一只老鼠吃掉,则得分的变化量是 reward}_1[i] - \textit{reward}_2[i]。
创建长度为 n 的数组 diffs,其中 diffs}[i] = \textit{reward}_1[i] - \textit{reward}_2[i]。题目要求计算第一只老鼠恰好吃掉 k 块奶酪的情况下的最大得分,假设第一只老鼠吃掉的 k 块奶酪的下标分别是 i_1 到 i_k,则总得分为:
\textit{sum} + \sum_{j = 1}^k \textit{diffs}[i_j]
其中 sum 为确定的值。根据贪心思想,为了使总得分最大化,应使下标 i_1 到 i_k 对应的 diffs 的值最大,即应该选择 diffs 中的 k 个最大值。
贪心思想的正确性说明如下:假设下标 i_1 到 i_k 对应的 diffs 的值不是最大的 k 个值,则一定存在下标 i_j 和下标 p 满足 diffs}[p] \ge \textit{diffs}[i_j] 且 p 不在 i_1 到 i_k 的 k 个下标中,将 diffs}[i_j] 替换成 diffs}[p] 之后的总得分不变或增加。因此使用贪心思想可以使总得分最大。
具体做法是,将数组 diffs 排序,然后计算 sum 与数组 diffs 的 k 个最大值之和,即为第一只老鼠恰好吃掉 k 块奶酪的情况下的最大得分。
[sol1-Java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { publicintmiceAndCheese(int[] reward1, int[] reward2, int k) { intans=0; intn= reward1.length; int[] diffs = newint[n]; for (inti=0; i < n; i++) { ans += reward2[i]; diffs[i] = reward1[i] - reward2[i]; } Arrays.sort(diffs); for (inti=1; i <= k; i++) { ans += diffs[n - i]; } return ans; } }
[sol1-C#]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
publicclassSolution { publicintMiceAndCheese(int[] reward1, int[] reward2, int k) { int ans = 0; int n = reward1.Length; int[] diffs = newint[n]; for (int i = 0; i < n; i++) { ans += reward2[i]; diffs[i] = reward1[i] - reward2[i]; } Array.Sort(diffs); for (int i = 1; i <= k; i++) { ans += diffs[n - i]; } return ans; } }
[sol1-C++]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intmiceAndCheese(vector<int>& reward1, vector<int>& reward2, int k){ int ans = 0; int n = reward1.size(); vector<int> diffs(n); for (int i = 0; i < n; i++) { ans += reward2[i]; diffs[i] = reward1[i] - reward2[i]; } sort(diffs.begin(), diffs.end()); for (int i = 1; i <= k; i++) { ans += diffs[n - i]; } return ans; } };
[sol1-Python3]
1 2 3 4 5 6 7 8 9 10
classSolution: defmiceAndCheese(self, reward1: List[int], reward2: List[int], k: int) -> int: ans = 0 n = len(reward1) diffs = [reward1[i] - reward2[i] for i inrange(n)] ans += sum(reward2) diffs.sort() for i inrange(1, k+1): ans += diffs[n - i] return ans
[sol1-Go]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
funcmiceAndCheese(reward1 []int, reward2 []int, k int)int { ans := 0 n := len(reward1) diffs := make([]int, n) for i:= 0; i < n; i++ { ans += reward2[i] diffs[i] = reward1[i] - reward2[i] } sort.Ints(diffs) for i:=1; i <= k; i++ { ans += diffs[n - i] } return ans }
[sol1-JavaScript]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
var miceAndCheese = function(reward1, reward2, k) { let ans = 0; let n = reward1.length; let diffs = newArray(n); for (let i = 0; i < n; i++) { ans += reward2[i]; diffs[i] = reward1[i] - reward2[i]; } diffs.sort((a, b) => a - b); for (let i = 1; i <= k; i++) { ans += diffs[n - i]; } return ans; }
intmiceAndCheese(int* reward1, int reward1Size, int* reward2, int reward2Size, int k) { int ans = 0; int n = reward1Size; int diffs[n]; for (int i = 0; i < n; i++) { ans += reward2[i]; diffs[i] = reward1[i] - reward2[i]; } qsort(diffs, n, sizeof(int), cmp); for (int i = 1; i <= k; i++) { ans += diffs[n - i]; } return ans; }
空间复杂度:O(n),其中 n 是数组 reward}_1 和 reward}_2 的长度。需要创建长度为 n 的数组 diffs 并排序,数组需要 O(n) 的空间,排序需要 O(\log n) 的递归调用栈空间,因此空间复杂度是 O(n)。
方法二:贪心 + 优先队列
方法一当中,计算最大得分的做法是创建长度为 n 的数组 diffs,其中 diffs}[i] = \textit{reward}_1[i] - \textit{reward}_2[i],将数组 diffs 排序之后计算 sum 与数组 diffs 的 k 个最大值之和。也可以使用优先队列存储数组 diffs 中的 k 个最大值,优先队列的队首元素为最小元素,优先队列的空间是 O(k)。
用 sum 表示数组 reward}_2 的元素之和。同时遍历数组 reward}_1 和 reward}_2,当遍历到下标 i 时,执行如下操作。
将 reward}_1[i] - \textit{reward}_2[i] 添加到优先队列。
如果优先队列中的元素个数大于 k,则取出优先队列的队首元素,确保优先队列中的元素个数不超过 k。
遍历结束时,优先队列中有 k 个元素,为数组 reward}_1 和 reward}_2 的 k 个最大差值。计算 sum 与优先队列中的 k 个元素之和,即为第一只老鼠恰好吃掉 k 块奶酪的情况下的最大得分。
[sol2-Java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { publicintmiceAndCheese(int[] reward1, int[] reward2, int k) { intans=0; intn= reward1.length; PriorityQueue<Integer> pq = newPriorityQueue<Integer>(); for (inti=0; i < n; i++) { ans += reward2[i]; pq.offer(reward1[i] - reward2[i]); if (pq.size() > k) { pq.poll(); } } while (!pq.isEmpty()) { ans += pq.poll(); } return ans; } }
[sol2-C#]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
publicclassSolution { publicintMiceAndCheese(int[] reward1, int[] reward2, int k) { int ans = 0; int n = reward1.Length; PriorityQueue<int, int> pq = new PriorityQueue<int, int>(); for (int i = 0; i < n; i++) { ans += reward2[i]; pq.Enqueue(reward1[i] - reward2[i], reward1[i] - reward2[i]); if (pq.Count > k) { pq.Dequeue(); } } while (pq.Count > 0) { ans += pq.Dequeue(); } return ans; } }
classSolution { public: intmiceAndCheese(vector<int>& reward1, vector<int>& reward2, int k){ int ans = 0; int n = reward1.size(); priority_queue<int, vector<int>, greater<int>> pq; for (int i = 0; i < n; i++) { ans += reward2[i]; pq.emplace(reward1[i] - reward2[i]); if (pq.size() > k) { pq.pop(); } } while (!pq.empty()) { ans += pq.top(); pq.pop(); } return ans; } };
[sol2-Python3]
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: defmiceAndCheese(self, reward1: List[int], reward2: List[int], k: int) -> int: ans = 0 n = len(reward1) pq = [] for i inrange(n): ans += reward2[i] heappush(pq, reward1[i] - reward2[i]) iflen(pq) > k: heappop(pq) while pq: ans += heappop(pq) return ans
funcmiceAndCheese(reward1 []int, reward2 []int, k int)int { ans := 0 n := len(reward1) pq := &IntHeap{} heap.Init(pq) for i := 0; i < n; i++ { ans += reward2[i] diff := reward1[i] - reward2[i] heap.Push(pq, diff) if pq.Len() > k { heap.Pop(pq) } } for pq.Len() > 0 { ans += heap.Pop(pq).(int) } return ans }
sinkDown(index) { const element = this.heap[index]; const length = this.heap.length; while (true) { let leftChildIndex = 2 * index + 1; let rightChildIndex = 2 * index + 2; let leftChild, rightChild; let swap = null;
if (leftChildIndex < length) { leftChild = this.heap[leftChildIndex]; if (leftChild < element) { swap = leftChildIndex; } }
var miceAndCheese = function(reward1, reward2, k) { let ans = 0; let n = reward1.length; let pq = newHeap(); for (let i = 0; i < n; i++) { ans += reward2[i]; pq.push(reward1[i] - reward2[i]); if (pq.size() > k) { pq.poll(); } } while (!pq.isEmpty()) { ans += pq.poll(); } return ans; }
复杂度分析
时间复杂度:O(n \log k),其中 n 是数组 reward}_1 和 reward}_2 的长度,k 是第一只老鼠吃掉的奶酪块数。遍历两个数组的过程中,每个下标处的优先队列操作时间是 O(\log k),共需要 O(n \log k) 的时间,遍历数组之后计算优先队列中的 k 个元素之和需要 O(k \log k) 的时间,其中 k \le n,因此时间复杂度是 O(n \log k + k \log k) = O(n \log k)。