执行 K 次操作后的最大分数【LC2530】
给你一个下标从 0 开始的整数数组
nums
和一个整数k
。你的 起始分数 为0
。在一步 操作 中:
- 选出一个满足
0 <= i < nums.length
的下标i
,- 将你的 分数 增加
nums[i]
,并且- 将
nums[i]
替换为ceil(nums[i] / 3)
。返回在 恰好 执行
k
次操作后,你可能获得的最大分数。向上取整函数
ceil(val)
的结果是大于或等于val
的最小整数。
学到了
大顶堆+贪心
-
思路
- 每次操作尽量选择数组中的最大值,以获得最大分数
- 借助大顶堆存储数组元素,那么堆顶元素为每次操作的元素,每次操作完成后将
c
e
i
l
(
n
u
m
/
3
)
ceil(num/3)
ceil(num/3)放入堆中,向上取整的实现有以下三种
- 调用API:
Math.ceil(num/3.0)
- 判断余数是否为0:
num / 3 + (num % 3 == 0 ? 0 : 1)
- 加2后再进行整除:
(num + 2) / 3
- 调用API:
-
实现
class Solution { public long maxKelements(int[] nums, int k) { PriorityQueue<Integer> pq = new PriorityQueue<>((o1, o2) -> o2 - o1); long res = 0L; for (int num : nums){ pq.add(num); } for (int i = 0; i < k; i++){ int num = pq.poll(); res += num; // pq.add(num / 3 + (num % 3 == 0 ? 0 : 1)); pq.add((num + 2) / 3); pq.add((int)Math.ceil(num / 3.0)); } return res; } }
- 复杂度
- 时间复杂度: O ( n + k l o g n ) O(n+klogn) O(n+klogn)
- 空间复杂度: O ( n ) O(n) O(n)
- 复杂度
堆排序:原地堆化
-
思路:优化空间复杂度,将数组作为大顶堆
- 大顶堆中的子节点值均不大于父节点的值,即满足 n u m s [ i ] ≥ n u m s [ 2 ∗ i + 1 ] nums[i] \ge nums[2*i+1] nums[i]≥nums[2∗i+1], n u m s [ i ] ≥ n u m s [ 2 ∗ i + 2 ] nums[i] \ge nums[2*i+2] nums[i]≥nums[2∗i+2],并且堆化后数组中最大的元素为 n u m s [ 0 ] nums[0] nums[0]
- 堆化:将每个父节点进行下沉,判断子节点值是否不大于父节点的值
- 如果子节点的值大于父节点的值,那么将最大子节点与父节点进行交换,直至判断到叶子节点
- 如果子节点的值均小于父节点的值,那么不做操作
- 取 k k k个最大的元素:每进行一次操作,将 n u m s [ 0 ] nums[0] nums[0]变为 c e i l ( n u m s [ 0 ] / 3.0 ) ceil(nums[0]/3.0) ceil(nums[0]/3.0),然后下沉堆顶元素
-
实现
class Solution { public long maxKelements(int[] nums, int k) { heapify(nums); // 原地堆化(最大堆) long ans = 0; while (k-- > 0) { ans += nums[0]; // 堆顶 nums[0] = (nums[0] + 2) / 3; sink(nums, 0); // 堆化(只需要把 nums[0] 下沉) } return ans; } // 原地堆化(最大堆) // 堆化可以保证 h[0] 是堆顶元素,且 h[i] >= max(h[2*i+1], h[2*i+2]) private void heapify(int[] h) { // 下标 >= h.length / 2 的元素是二叉树的叶子,无需下沉 // 倒着遍历,从而保证 i 的左右子树一定是堆,那么 sink(h, i) 就可以把左右子树合并成一个堆 for (int i = h.length / 2 - 1; i >= 0; i--) { sink(h, i); } } // 把 h[i] 不断下沉,直到 i 的左右儿子都 <= h[i] private void sink(int[] h, int i) { int n = h.length; while (2 * i + 1 < n) { int j = 2 * i + 1; // i 的左儿子 if (j + 1 < n && h[j + 1] > h[j]) { // i 的右儿子比 i 的左儿子大 j++; } if (h[j] <= h[i]) { // 说明 i 的左右儿子都 <= h[i],停止下沉 break; } swap(h, i, j); // 下沉 i = j; } } // 交换 h[i] 和 h[j] private void swap(int[] h, int i, int j) { int tmp = h[i]; h[i] = h[j]; h[j] = tmp; } } 作者:灵茶山艾府 链接:https://leetcode.cn/problems/maximal-score-after-applying-k-operations/solutions/2487446/o1-kong-jian-zuo-fa-pythonjavacgojsrust-ztx6f/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 复杂度
- 时间复杂度: O ( n + k l o g n ) O(n+klogn) O(n+klogn)
- 空间复杂度: O ( 1 ) O(1) O(1)
- 复杂度