[周赛传送门]leetcode-cn.com/contest/wee…
和冠军的差距太太太太大啦,我过第二题的时候,冠军已经早早做完了。
5894. 至少在两个数组中出现的值
思路:暴力查找
时间复杂度:。 是取值范围的长度, 是三个数组的元素数量。
由于各数组中元素的取值范围为 ,因此可枚举 ,然后检查 是否在至少两个数组中出现即可。
class Solution {
public:
vector<int> twoOutOfThree(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3) {
vector<int> anw;
for (int i = 1; i <= 100; i++) {
int cnt = 0;
cnt += (std::find(nums1.begin(), nums1.end(), i) != nums1.end());
cnt += (std::find(nums2.begin(), nums2.end(), i) != nums2.end());
cnt += (std::find(nums3.begin(), nums3.end(), i) != nums3.end());
if (cnt >= 2) {
anw.push_back(i);
}
}
return anw;
}
};
5895. 获取单值网格的最小操作数
思路:前缀和,等差数列
时间复杂度:。 是取值范围的长度。
不难想到,当且仅当「 个数字在去重后能组成公差为 x 的等差序列」,才可以通过若干次操作获得单值网格。
首先检查能否组成上述序列。这个不难,可以 的实现。设有一个一维数组 , 表示数字 在 中出现次数。
统计完 后,只需检查 中所有不为零元素的下标能否组成公差为 的等差序列即可。
接下来,问题变为了「给出长度为 的等差序列 ,将其中数字变为单值的最小次数」。
不难想到,将 中数字都变为 的操作次数为 :
通过预处理前缀和以及后缀和,可以 的计算出所有的 。显然,其中最小的 即为答案。预处理过程详见注释。
class Solution {
public:
int minOperations(vector<vector<int>>& grid, int x) {
// 比取值上限大一,用于定义数组。
const int MAXN = 10001;
// cnt[i] 表示数字 i 在 grid 中出现的次数
int cnt[MAXN] = {0};
// num 表示在 grid 一共出现了多少种数字
int num = 0;
// 记录一下出现的最小值
int minNum = INT_MAX;
// 记录一下出现的最大值
int maxNum = INT_MIN;
// 更新 cnt 以及 num
for (const auto &row : grid) {
for (const auto &c : row) {
if(cnt[c] == 0) {
// 如果 cnt[c] 为 0,说明出现了一种新数字。
num++;
}
cnt[c]++;
minNum = min(minNum, c);
maxNum = max(maxNum, c);
}
}
// 检查所有出现的数字在去重后能否组成等差序列。
// 从 minNum 开始检查 minNum,minNum+x,minNum+2x,...,minNum+(num-1)*x 是否都出现过。
for (int j = minNum; j < MAXN; j += x) {
if (cnt[j] > 0) {
num--;
}
}
// 不是关于 x 的等差序列,直接返回 -1 吧
if (num != 0) {
return -1;
}
// suf[i] 表示把不小于 i 的数字都变为 i 的花销。
// sufCnt[i] 表示不小于 i 的数字一共出现了多少次。
int suf[MAXN] = {0};
int sufCnt[MAXN] = {0};
for (int i = maxNum; i >= minNum; i -= x) {
sufCnt[i] = cnt[i];
if (i+x < MAXN) {
sufCnt[i] += sufCnt[i+x];
suf[i] = suf[i+x] + sufCnt[i+x];
}
}
int anw = INT_MAX;
// pre[i] 表示把不大于 i 的数字都变为 i 的花销。
// preCnt[i] 表示不大于 i 的数字一共出现了多少次。
int pre[MAXN] = {0};
int preCnt[MAXN] = {0};
for (int i = minNum; i <= maxNum; i += x) {
preCnt[i] = cnt[i];
if (i >= x) {
preCnt[i] += preCnt[i-x];
pre[i] = pre[i-x] + preCnt[i-x];
}
if (cnt[i] > 0) {
anw = min(anw, pre[i] + suf[i]);
}
}
return anw;
}
};
5896. 股票价格波动
思路:multimap
时间复杂度:查询为 ,更新为 。
首先使用 记录时间戳和价格的映射关系。这样对于 current 查询,map.rbegin() 即为答案。时间复杂度为 。
使用 保存有效的价格,对于 maximum 查询,multimap.rbegin() 即为答案,对于 minimum 查询,minimum.begin() 即为答案。时间复杂度均为 。
每次 update 操作,仅需对这两个容器进行常数次的增删改。时间复杂度为 。
class StockPrice {
public:
map<int, int> t2p; // 保存时间和价格的映射
multiset<int> price; // 保存已知的有效价格
StockPrice() {
}
void update(int t, int p) {
auto it = t2p.find(t);
// 没有找到,说明是个新的时间戳,直接加入到 t2p 和 price
if (it == t2p.end()) {
t2p[t] = p;
price.insert(p);
return ;
}
// 是个修改操作,先删除错误的价格
auto pit = price.find(it->second);
price.erase(pit);
// 插入更新后的价格
price.insert(p);
// 更新 t2p
it->second = p;
}
int current() {
return t2p.rbegin()->second;
}
int maximum() {
return *price.rbegin();
}
int minimum() {
return *price.begin();
}
};
5897. 将数组分成两个数组并最小化数组和的差
思路:位运算,三分
时间复杂度:
不难想到,先选 个数字构成数组一,那么剩下的 的数字就是数组二了。
接下来,将数组一的选取过程分成两步:
- 在前 个数字中选取 个。
- 在后 个数字中选取 个。
两步各有 种选取方案,组合起来就是。尝试用三分优化一下组合过程。
假设在前一半中选取了 个数字,其和为 ,则后一半中有 种待选方案,假设我们选取了第 k 种方案,其和为 。则两数组的差值可表示为
不难发现,随着 从小到大,上述式子的值可能会先变小后增大,即上式是一个凹函数。因此可用三分找出关于 的最优的 。
class Solution {
public:
void work(const vector<int> &part, unordered_map<int, vector<int>> &c) {
int n = part.size();
// 位运算处理 2^n 种方案
for (int i = 0, m = (1<<n); i < m; i++) {
// 记录第 i 种方案的累加和
int sum = 0;
// 记录第 i 种方案的选取的数字个数
int cnt = 0;
for (int j = 0, bit = 1; j < n; j++, bit <<= 1) {
if (i&bit) {
sum += part[j];
cnt++;
}
}
// 记录第 i 种方案
c[cnt].push_back(sum);
}
// 排个序,方便后续的三分操作
for (auto &p : c) {
sort(p.second.begin(), p.second.end());
}
}
// 三分
int find(int v, int sum, const std::vector<int> &arr) {
int l = 0, r = arr.size()-1;
int anw = INT_MAX;
while(l <= r) {
int mid = (l+r)>>1;
int mmid = (mid+r)>>1;
int c = abs(2*(v+arr[mid]) - sum);
int cc = abs(2*(v+arr[mmid]) - sum);
if (c <= cc) {
r = mmid-1;
} else {
l = mid+1;
}
anw = min(anw, min(c, cc));
}
return anw;
}
int minimumDifference(vector<int>& nums) {
int n = nums.size()/2;
// 计算 n 个数字之和
int sum = 0;
for_each(nums.begin(), nums.end(), [&sum](int v) {sum += v;});
// pre[i] 记录在前 n 个数字中选取 i 个数字的C(n,i)种方案
// suf[i] 记录在后 n 个数字中选取 i 个数字的C(n,i)种方案
unordered_map<int, vector<int>> pre, suf;
// 处理 pre
work(std::vector<int>(nums.begin(), nums.begin()+n), pre);
// 处理 suf
work(std::vector<int>(nums.begin()+n, nums.end()), suf);
// anw 用于存储答案
int anw = INT_MAX;
// 「数组一」在前 n 个数字中的选取了 i 个数字。
for (int i = 0; i <= n; i++) {
// 枚举 C(n,i) 种方案
for (auto p : pre[i]) {
// 「数组一」必然在后 n 个数字中选取 n-i 个数字,利用三分查找最优解。
anw = min(anw, find(p, sum, suf[n-i]));
}
}
return anw;
}
};