Medium
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个整数 k。你的起始分数为 0。
在一次操作中:
- 选择一个满足
0 <= i < nums.length的下标i, - 将你的分数增加
nums[i],并 - 将
nums[i]替换为ceil(nums[i] / 3)。
返回在 恰好 执行 k 次操作后,你能够获得的最大分数。
向上取整函数 ceil(val) 是大于或等于 val 的最小整数。
示例 1:
输入:nums = [10,10,10,10,10], k = 5
输出:50
解释:对数组每个元素恰好执行一次操作。最终分数是 10 + 10 + 10 + 10 + 10 = 50 。
示例 2:
输入:nums = [1,10,3,3,3], k = 3
输出:17
解释:可以执行下述操作:
操作 1:选择 i = 1 ,nums 变为 [1,4,3,3,3] 。你的分数增加 10 。
操作 2:选择 i = 1 ,nums 变为 [1,2,3,3,3] 。你的分数增加 4 。
操作 3:选择 i = 2 ,nums 变为 [1,2,1,3,3] 。你的分数增加 3 。
最终分数是 10 + 4 + 3 = 17 。
提示:
1 <= nums.length, k <= 10^51 <= nums[i] <= 10^9
解题思路
这是一道典型的贪心 + 优先队列问题。
核心思路:每次操作都应该选择当前数组中的最大值,因为选择更大的值能获得更多分数。这符合贪心策略的局部最优原则。
算法步骤:
- 使用最大堆(优先队列)来维护数组中的所有元素,确保能在 O(log n) 时间内获取最大值
- 执行 k 次操作,每次操作:
- 取出堆顶的最大元素,将其加入总分数
- 将该元素除以3并向上取整后重新插入堆中
- 返回累积的总分数
时间复杂度分析:初始建堆需要 O(n),每次操作需要 O(log n),总共 k 次操作,所以整体时间复杂度为 O(n + k log n)。
注意事项:
- 需要使用 long long 或对应语言的长整型来避免溢出
- 向上取整可以用
(x + 2) / 3的技巧来实现,避免浮点运算
这种贪心策略是最优的,因为每一步都选择当前能获得最大收益的元素。
代码实现
class Solution {
public:
long long maxKelements(vector<int>& nums, int k) {
priority_queue<int> pq(nums.begin(), nums.end());
long long score = 0;
for (int i = 0; i < k; i++) {
int maxVal = pq.top();
pq.pop();
score += maxVal;
pq.push((maxVal + 2) / 3);
}
return score;
}
};
class Solution:
def maxKelements(self, nums: List[int], k: int) -> int:
import heapq
# Python的heapq是最小堆,所以用负数来实现最大堆
heap = [-num for num in nums]
heapq.heapify(heap)
score = 0
for _ in range(k):
max_val = -heapq.heappop(heap)
score += max_val
heapq.heappush(heap, -((max_val + 2) // 3))
return score
public class Solution {
public long MaxKelements(int[] nums, int k) {
var pq = new PriorityQueue<int, int>(Comparer<int>.Create((x, y) => y.CompareTo(x)));
foreach (int num in nums) {
pq.Enqueue(num, num);
}
long score = 0;
for (int i = 0; i < k; i++) {
int maxVal = pq.Dequeue();
score += maxVal;
int newVal = (maxVal + 2) / 3;
pq.Enqueue(newVal, newVal);
}
return score;
}
}
var maxKelements = function(nums, k) {
// JavaScript没有内置的优先队列,使用数组模拟
const heap = [...nums];
// 建立最大堆
const buildMaxHeap = (arr) => {
for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
heapify(arr, i, arr.length);
}
};
const heapify = (arr, i, n) => {
let largest = i;
let left = 2 * i + 1;
let right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, largest, n);
}
};
const extractMax = (arr) => {
const max = arr[0];
arr[0] = arr[arr.length - 1];
arr.pop();
heapify(arr, 0, arr.length);
return max;
};
const insertHeap = (arr, val) => {
arr.push(val);
let i = arr.length - 1;
while (i > 0 && arr[Math.floor((i - 1) / 2)] < arr[i]) {
[arr[i], arr[Math.floor((i - 1) / 2)]] = [arr[Math.floor((i - 1) / 2)], arr[i]];
i = Math.floor((i - 1) / 2);
}
};
buildMaxHeap(heap);
let score = 0;
for (let i = 0; i < k; i++) {
const maxVal = extractMax(heap);
score += maxVal;
insertHeap(heap, Math.ceil(maxVal / 3));
}
return score;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n + k log n) | 建堆 O(n),k 次操作每次 O(log n) |
| 空间复杂度 | O(n) | 优先队列存储 n 个元素 |
相关题目
. Sliding Window Maximum (Hard)