1674. 使数组互补的最少操作次数
LeetCode第217周赛的第三题,比赛时卡了一个小时,没有想到O(n)的做法。对差分不熟悉,但是最关键的还是扫描的思路没有想到。由于这道题有这么几个点比较重要,觉得应该特别记录一下。
- 扫描:比赛时我也想到了当选定和K处于个个区间[2, lo]、[lo, hi]、[sum, sum]时的状态。可是我把对于每个nums[i]的lo、hi、sum都看成独立的一套。没有联系起来。题解里面把所有数放在数轴上的思路是我第一次见到这种思想。
- 差分:这题不一定一定要用差分,只是利用了差分的区间更新、单点查询的性质。实际上也可也用树状数组,或者更复杂的线段树(还没学过)。只是这里差分就可以完成。
//
// Created by root on 2020/11/30.
//
#include <vector>
using namespace std;
class Solution {
public:
// 差分数组:b[k]。
// 差分数组更新,对[l, r]区间++:b[l]++, b[r + 1]--;
// 和为k时最小操作次数即b[0] + b[1] + ... + b[k]
vector<int> b;
void update(int l, int r, int x) {
b[l] += x;
b[r + 1] -= x;
}
int minMoves(vector<int>& nums, int limit) {
b.resize(limit * 2 + 2);
int n = nums.size();
for (int i = 0; i < n / 2; i++) {
int lo = min(nums[i], nums[n - i - 1]) + 1; // 单步操作可以达到的最小值就是两者之min + 1
int hi = max(nums[i], nums[n - i - 1]) + limit; // 最大值就是两者之max + limit
int sum = nums[i] + nums[n - i - 1]; // 当前和
// 想象一个数轴,K在移动:
// lo <= K <= hi时,操作次数为1
// K = sum时,操作次数为0
// 其余情况,操作次数为2,为方便起见,我们设初始步数就为2,这样对其他操作我们只需要--
update(lo, hi, -1); // 对[lo, hi]中操作次数--
update(sum, sum, -1); // 对[sum, sum]中操作次数继续--
}
// 最大操作步数为 n / 2 * 2
int now = n;
int res = n;
// 数组互补时,选中的和K的范围[2, limit * 2 + 2]
for (int i = 2; i < limit * 2 + 2; i++) {
now += b[i];
res = min(res, now);
}
return res;
}
};
附上树状数组解法:
// 树状数组解法:区间更新、单点查询
class BIT {
public:
vector<int> trees;
int max_n;
BIT(int n) {
trees.resize(n);
max_n = n;
}
int lowBit(int x) {
return x & (-x);
}
void update(int pos, int x) {
for (int i = pos; i < max_n; i += lowBit(i)) {
trees[i] += x;
}
}
int query(int pos) {
int res = 0;
for (int i = pos; i; i -= lowBit(i)) {
res += trees[i];
}
return res;
}
// 引入差分,使树状数组可以进行区间更新
void update(int l, int r, int x) {
update(l, x);
update(r + 1, -x);
}
};
class Solution2 {
public:
int minMoves(vector<int>& nums, int limit) {
BIT bit(limit * 2 + 2);
int n = nums.size();
for (int i = 0; i < n / 2; i++) {
int lo = min(nums[i], nums[n - i - 1]) + 1;
int hi = max(nums[i], nums[n - i - 1]) + limit;
int sum = nums[i] + nums[n - i - 1];
bit.update(lo, hi, -1);
bit.update(sum, sum, -1);
}
// 由于树状数组的区间查询不需要遍历,因此这里now直接记录K == i时,减去的操作数。因此,答案需要返回n + res
int now = 0;
int res = n;
// 数组互补时,选中的和K的范围[2, limit * 2 + 2]
for (int i = 2; i < limit * 2 + 2; i++) {
now = bit.query(i);
res = min(res, now);
}
return n + res;
}
};
这道题让我对树状数组的使用理解又加深了一些,后面可能会总结一下树状数组的使用和前缀和、差分、树状数组这些简单的数据结构的区别和功能。