|
55 | 55 |
|
56 | 56 | ## 解法
|
57 | 57 |
|
| 58 | +### 方法一:优先队列(大小根堆) |
| 59 | + |
| 60 | +我们不妨假设最终的数组元素为 $x, x+1, x+2, \cdots, x+n-1$,那么操作次数为 $|nums[0] - x| + |nums[1] - (x+1)| + \cdots + |nums[n-1] - (x+n-1)|$。我们不妨变换一下式子,得到 $|nums[0] - x| + |nums[1] - 1 - x| + \cdots + |nums[n-1] - (n-1) - x|$。 |
| 61 | + |
| 62 | +如果我们将 $nums[i] - i$ 作为第 $i$ 个元素 $nums[i]$,那么上述式子就变成了求 $|nums[0] - x| + |nums[1] - x| + \cdots + |nums[n-1] - x|$ 的最小值。这等价于求:把数组 $nums$ 操作成相同数字的最小操作次数,即把数组 $nums$ 操作成中位数的最小操作数。 |
| 63 | + |
| 64 | +我们可以用大小根堆,动态维护前缀数组的中位数。 |
| 65 | + |
| 66 | +具体地,我们创建大根堆、小根堆,其中:小根堆 $q1$ 存放较大的一半元素,大根堆 $q2$ 存放较小的一半元素。 |
| 67 | + |
| 68 | +添加元素时,先放入小根堆,然后将小根堆对顶元素弹出并放入大根堆(使得大根堆个数多 $1$);若大小根堆元素个数差超过 $1$,则将大根堆元素弹出放入小根堆。过程中,用两个变量 $s1, s2$ 分别记录两个堆的元素和。 |
| 69 | + |
| 70 | +取中位数时,若大根堆元素较多,取大根堆堆顶,否则取两堆顶元素和的平均值。求出中位数 $x$ 后,当前最小操作次数为 $s1 - x \times |q1| + x \times |q2| - s2$。注意,我们需要对最小操作次数取模。 |
| 71 | + |
| 72 | +时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 为数组长度。 |
| 73 | + |
| 74 | +<!-- tabs:start --> |
| 75 | + |
| 76 | +```python |
| 77 | +class MedianFinder: |
| 78 | + def __init__(self): |
| 79 | + self.q1 = [] |
| 80 | + self.q2 = [] |
| 81 | + self.s1 = 0 |
| 82 | + self.s2 = 0 |
| 83 | + |
| 84 | + def addNum(self, num: int) -> None: |
| 85 | + heappush(self.q1, num) |
| 86 | + self.s1 += num |
| 87 | + num = heappop(self.q1) |
| 88 | + heappush(self.q2, -num) |
| 89 | + self.s1 -= num |
| 90 | + self.s2 += num |
| 91 | + if len(self.q2) - len(self.q1) > 1: |
| 92 | + num = -heappop(self.q2) |
| 93 | + heappush(self.q1, num) |
| 94 | + self.s1 += num |
| 95 | + self.s2 -= num |
| 96 | + |
| 97 | + def findMedian(self) -> int: |
| 98 | + if len(self.q2) > len(self.q1): |
| 99 | + return -self.q2[0] |
| 100 | + return (self.q1[0] - self.q2[0]) // 2 |
| 101 | + |
| 102 | + def cal(self) -> int: |
| 103 | + x = self.findMedian() |
| 104 | + return (self.s1 - x * len(self.q1) + x * len(self.q2) - self.s2) % (10**9 + 7) |
| 105 | + |
| 106 | + |
| 107 | +class Solution: |
| 108 | + def numsGame(self, nums: List[int]) -> List[int]: |
| 109 | + n = len(nums) |
| 110 | + ans = [0] * n |
| 111 | + finder = MedianFinder() |
| 112 | + for i, x in enumerate(nums): |
| 113 | + finder.addNum(x - i) |
| 114 | + ans[i] = finder.cal() |
| 115 | + return ans |
| 116 | +``` |
| 117 | + |
| 118 | +```java |
| 119 | +class MedianFinder { |
| 120 | + private PriorityQueue<Integer> q1 = new PriorityQueue<>(); |
| 121 | + private PriorityQueue<Integer> q2 = new PriorityQueue<>(Collections.reverseOrder()); |
| 122 | + private final int mod = (int) 1e9 + 7; |
| 123 | + private long s1; |
| 124 | + private long s2; |
| 125 | + |
| 126 | + public MedianFinder() { |
| 127 | + } |
| 128 | + |
| 129 | + public void addNum(int num) { |
| 130 | + q1.offer(num); |
| 131 | + s1 += num; |
| 132 | + num = q1.poll(); |
| 133 | + q2.offer(num); |
| 134 | + s1 -= num; |
| 135 | + s2 += num; |
| 136 | + if (q2.size() - q1.size() > 1) { |
| 137 | + num = q2.poll(); |
| 138 | + q1.offer(num); |
| 139 | + s1 += num; |
| 140 | + s2 -= num; |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + public long findMedian() { |
| 145 | + if (q2.size() > q1.size()) { |
| 146 | + return q2.peek(); |
| 147 | + } |
| 148 | + return (q1.peek() + q2.peek()) / 2; |
| 149 | + } |
| 150 | + |
| 151 | + public int cal() { |
| 152 | + long x = findMedian(); |
| 153 | + return (int) ((s1 - x * q1.size() + x * q2.size() - s2) % mod); |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +class Solution { |
| 158 | + public int[] numsGame(int[] nums) { |
| 159 | + int n = nums.length; |
| 160 | + int[] ans = new int[n]; |
| 161 | + MedianFinder finder = new MedianFinder(); |
| 162 | + for (int i = 0; i < n; ++i) { |
| 163 | + finder.addNum(nums[i] - i); |
| 164 | + ans[i] = finder.cal(); |
| 165 | + } |
| 166 | + return ans; |
| 167 | + } |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +```cpp |
| 172 | +class MedianFinder { |
| 173 | +public: |
| 174 | + MedianFinder() { |
| 175 | + } |
| 176 | + |
| 177 | + void addNum(int num) { |
| 178 | + q1.push(num); |
| 179 | + s1 += num; |
| 180 | + num = q1.top(); |
| 181 | + q2.push(num); |
| 182 | + q1.pop(); |
| 183 | + s2 += num; |
| 184 | + s1 -= num; |
| 185 | + if (q2.size() - q1.size() > 1) { |
| 186 | + num = q2.top(); |
| 187 | + q1.push(num); |
| 188 | + q2.pop(); |
| 189 | + s1 += num; |
| 190 | + s2 -= num; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + int findMedian() { |
| 195 | + if (q2.size() > q1.size()) { |
| 196 | + return q2.top(); |
| 197 | + } |
| 198 | + return (q1.top() + q2.top()) / 2; |
| 199 | + } |
| 200 | + |
| 201 | + int cal() { |
| 202 | + long long x = findMedian(); |
| 203 | + return (s1 - x * q1.size() + x * q2.size() - s2) % mod; |
| 204 | + } |
| 205 | + |
| 206 | +private: |
| 207 | + priority_queue<int, vector<int>, greater<int>> q1; |
| 208 | + priority_queue<int> q2; |
| 209 | + long long s1 = 0; |
| 210 | + long long s2 = 0; |
| 211 | + const int mod = 1e9 + 7; |
| 212 | +}; |
| 213 | + |
| 214 | +class Solution { |
| 215 | +public: |
| 216 | + vector<int> numsGame(vector<int>& nums) { |
| 217 | + int n = nums.size(); |
| 218 | + vector<int> ans(n); |
| 219 | + MedianFinder finder; |
| 220 | + for (int i = 0; i < n; ++i) { |
| 221 | + finder.addNum(nums[i] - i); |
| 222 | + ans[i] = finder.cal(); |
| 223 | + } |
| 224 | + return ans; |
| 225 | + } |
| 226 | +}; |
| 227 | +``` |
| 228 | +
|
| 229 | +```go |
| 230 | +func numsGame(nums []int) []int { |
| 231 | + n := len(nums) |
| 232 | + ans := make([]int, n) |
| 233 | + finder := newMedianFinder() |
| 234 | + for i, x := range nums { |
| 235 | + finder.AddNum(x - i) |
| 236 | + ans[i] = finder.Cal() |
| 237 | + } |
| 238 | + return ans |
| 239 | +} |
| 240 | +
|
| 241 | +type MedianFinder struct { |
| 242 | + q1 hp |
| 243 | + q2 hp |
| 244 | + s1 int |
| 245 | + s2 int |
| 246 | +} |
| 247 | +
|
| 248 | +func newMedianFinder() *MedianFinder { |
| 249 | + return &MedianFinder{hp{}, hp{}, 0, 0} |
| 250 | +} |
| 251 | +
|
| 252 | +func (this *MedianFinder) AddNum(num int) { |
| 253 | + heap.Push(&this.q1, num) |
| 254 | + this.s1 += num |
| 255 | + num = heap.Pop(&this.q1).(int) |
| 256 | + heap.Push(&this.q2, -num) |
| 257 | + this.s1 -= num |
| 258 | + this.s2 += num |
| 259 | + if this.q2.Len()-this.q1.Len() > 1 { |
| 260 | + num = -heap.Pop(&this.q2).(int) |
| 261 | + heap.Push(&this.q1, num) |
| 262 | + this.s1 += num |
| 263 | + this.s2 -= num |
| 264 | + } |
| 265 | +} |
| 266 | +
|
| 267 | +func (this *MedianFinder) FindMedian() int { |
| 268 | + if this.q2.Len() > this.q1.Len() { |
| 269 | + return -this.q2.IntSlice[0] |
| 270 | + } |
| 271 | + return (this.q1.IntSlice[0] - this.q2.IntSlice[0]) / 2 |
| 272 | +} |
| 273 | +
|
| 274 | +func (this *MedianFinder) Cal() int { |
| 275 | + x := this.FindMedian() |
| 276 | + return (this.s1 - x*this.q1.Len() + x*this.q2.Len() - this.s2) % 1000000007 |
| 277 | +} |
| 278 | +
|
| 279 | +type hp struct{ sort.IntSlice } |
| 280 | +
|
| 281 | +func (h hp) Less(i, j int) bool { return h.IntSlice[i] < h.IntSlice[j] } |
| 282 | +func (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) } |
| 283 | +func (h *hp) Pop() any { |
| 284 | + a := h.IntSlice |
| 285 | + v := a[len(a)-1] |
| 286 | + h.IntSlice = a[:len(a)-1] |
| 287 | + return v |
| 288 | +} |
| 289 | +``` |
| 290 | + |
| 291 | +<!-- tabs:end --> |
| 292 | + |
58 | 293 | <!-- end -->
|
0 commit comments