目录
LCP 24. 数字游戏
题目描述:
实现代码与解析:
原理思路:
小扣在秋日市集入口处发现了一个数字游戏。主办方共有 N
个计数器,计数器编号为 0 ~ N-1
。每个计数器上分别显示了一个数字,小扣按计数器编号升序将所显示的数字记于数组 nums
。每个计数器上有两个按钮,分别可以实现将显示数字加一或减一。小扣每一次操作可以选择一个计数器,按下加一或减一按钮。
主办方请小扣回答出一个长度为 N
的数组,第 i
个元素(0 <= i < N)表示将 0~i
号计数器 初始 所示数字操作成满足所有条件 nums[a]+1 == nums[a+1],(0 <= a < i)
的最小操作数。回答正确方可进入秋日市集。
由于答案可能很大,请将每个最小操作数对 1,000,000,007
取余。
示例 1:
输入:
nums = [3,4,5,1,6,7]
输出:
[0,0,0,5,6,7]
解释: i = 0,[3] 无需操作 i = 1,[3,4] 无需操作; i = 2,[3,4,5] 无需操作; i = 3,将 [3,4,5,1] 操作成 [3,4,5,6], 最少 5 次操作; i = 4,将 [3,4,5,1,6] 操作成 [3,4,5,6,7], 最少 6 次操作; i = 5,将 [3,4,5,1,6,7] 操作成 [3,4,5,6,7,8],最少 7 次操作; 返回 [0,0,0,5,6,7]。
示例 2:
输入:
nums = [1,2,3,4,5]
输出:
[0,0,0,0,0]
解释:对于任意计数器编号 i 都无需操作。
示例 3:
输入:
nums = [1,1,1,2,3,4]
输出:
[0,1,2,3,3,3]
解释: i = 0,无需操作; i = 1,将 [1,1] 操作成 [1,2] 或 [0,1] 最少 1 次操作; i = 2,将 [1,1,1] 操作成 [1,2,3] 或 [0,1,2],最少 2 次操作; i = 3,将 [1,1,1,2] 操作成 [1,2,3,4] 或 [0,1,2,3],最少 3 次操作; i = 4,将 [1,1,1,2,3] 操作成 [-1,0,1,2,3],最少 3 次操作; i = 5,将 [1,1,1,2,3,4] 操作成 [-1,0,1,2,3,4],最少 3 次操作; 返回 [0,1,2,3,3,3]。
对顶堆求中位数
class Solution {
public int[] numsGame(int[] nums) {
final int MOD = 1000000007;
PriorityQueue lq = new PriorityQueue<>((a, b) -> b - a); // 大根堆
PriorityQueue rq = new PriorityQueue<>(); // 小根堆
int n = nums.length;
int[] res = new int[n];
long lsum = 0;
long rsum = 0;
for (int i = 0; i < n; i++) {
int t = nums[i] - i;
if (i % 2 == 0) {
lq.offer(t);
lsum += t;
int j = lq.peek();
lq.poll();
lsum -= j;
rq.offer(j);
rsum += j;
res[i] = (int) ((rsum - rq.peek() - lsum) % MOD);
} else {
rq.offer(t);
rsum += t;
int j = rq.peek();
rq.poll();
rsum -= j;
lq.offer(j);
lsum += j;
res[i] = res[i] = (int) ((rsum - lsum) % MOD);
}
}
return res;
}
}
题目转化:就是所有nums[i] - i 变成相同值的最小和,而这个相同值就是他们的中位数,把这些数放在数轴上就可以很好的观察出来,也可以作为定理记下来。
至于为啥是num[i] - i,可以简单证明一下。例如有一个值 a0, a1, a2,假设最后结果为b , b + 1, b + 2,那么和就为|a0 - (b + 0)| + |a1 - (b + 1)| + |a2 - (b + 2)| 等价于sum|(ai - i - b)|,把ai - i 看成一个整体ti,那么就变成了每一个 ti 到某一个值的距离最小和。这个值就是中位数。
所以这题的核心就是求中位数,如何求?
就是对顶堆(双优先级队列lq,rq)
- 中位数左的小数用lq大顶堆维护
- 中位数右的大数用rq小顶堆维护
- x 为我们正在遍历的数
- 如果前缀长度是奇数,此时 lq 和 rq 大小相等,我们先把 x 插入 lq,然后弹出 lq 的堆顶,加到 rq 中。这一操作可以保证,无论 x 是大是小,此时 rq 的堆顶就是中位数。
- 如果前缀长度是偶数,此时 lq 比 rq 少一个元素,我们先把 x 插入 rq,然后弹出 rq 的堆顶,加到 lq 中。
当然这里是可以优化的,比如奇数时,lq里的最大值都小于x,那么就直接放进rq就好了,就省去了进lq,再出来进rq的步骤,偶数时同理。
这题只要想到题目转换的含义就很好写了。