暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Leetcode第262场周赛

fighting小王子 2021-10-10
182

周末快乐。今天周赛,第一题直接模拟,用了一个时间复杂度比较高的方法;第二题贪心;第三题考数据结构,个人认为借助C++ STL库可以大大降低这类题的难度,许多实现细节可以不关注,从而可以快速解决;第四题状态压缩+二分(此"二分"非彼二分)。今天的代码敲了些空格,看起来舒服些,开始培养这个习惯。


keywords:模拟;贪心;数据结构;状态压缩;二分

5894. 至少在两个数组中出现的值


【题目描述】


【思路】

数据规模比较小,直接排序去重,然后统计每个元素出现次数。这样的时间复杂度应该是o(nlogn),也可以把vector转存到set实现去重,空间换时间。


【代码】
class Solution {
public:
int cnt[110];
vector<int> twoOutOfThree(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3) {
memset(cnt , 0 , sizeof cnt);
vector<int> ans;


sort(nums1.begin() , nums1.end());
nums1.erase(unique(nums1.begin() , nums1.end()) , nums1.end());
for(auto &x : nums1)
cnt[x] ++;

sort(nums2.begin() , nums2.end());
nums2.erase(unique(nums2.begin() , nums2.end()) , nums2.end());
for(auto &x : nums2)
cnt[x] ++;

sort(nums3.begin() , nums3.end());
nums3.erase(unique(nums3.begin() , nums3.end()) , nums3.end());
for(auto &x : nums3)
cnt[x] ++;

for(int i = 1; i <= 100; i++)
if(cnt[i] >= 2) ans.push_back(i);
return ans;
}
};
复制

5895. 获取单值网格的最小操作数


【题目描述】


【思路】

说来也奇怪,做的时候直接展成一维,然后取中位数计算能不能转成单值网格,没多想。回头琢磨,确实是贪心的思路,只是当时没注意。

首先操作次数要最少,那么最后转换成的那个元素一定是网格里现有的,这很显然。若不然,转成了一个比网格中所有元素都大的数,那需要的操作次数一定比转成网格中最大的数需要的次数多。另外,转成网格中哪个数操作次数最少,可以这样思考,目的是要把所有元素移动到同一位置,都移到中位数一定是整体移动距离最少的(没有严格的数学证明...)。

代码写得比较长,但实际就做了两件事,找中位数,然后统计将其他数变成中位数需要的操作。代码长是因为分了元素为奇数和偶数个两种情况,偶数个元素时还单独算了一遍转成第n和第n+1个元素需要的操作数,取最小。


【代码】
class Solution {
public:
int minOperations(vector<vector<int>>& grid, int x) {
vector<int> num;
int ans = 9999999999;
for(auto &x : grid)
{
for(auto &y : x)
num.push_back(y);
}
sort( num.begin() , num.end() );
int n = num.size();
if(n%2 == 0)
{
int id1 = n / 2 , id2 = n / 2 - 1;
bool flag = true;
int cnt = 0;
for(int i = 0; i < n; i ++)
{
if(i != id1)
{
if(abs(num[i] - num[id1]) % x == 0) cnt += abs(num[i] - num[id1]) / x;
else flag = false;
}
if(flag == false) break;
}
if( flag )
ans = min( cnt , ans );
cnt = 0; flag = true;
for(int i = 0; i < n; i ++)
{
if(i != id2)
{
if(abs(num[i] - num[id2]) % x == 0) cnt += abs(num[i] - num[id2]) / x;
else flag = false;
}
if(flag == false) break;
}
if( !flag ) return -1;
else ans = min( ans , cnt );
}
else
{
int id = n / 2;
bool flag = true;
int cnt = 0;
for(int i = 0; i < n; i ++)
{
if(i != id)
{
if(abs(num[i] - num[id]) % x == 0) cnt += abs(num[i] - num[id]) / x;
else flag = false;
}
if(flag == false) return -1;
}
ans = min( ans , cnt );
}
return ans;
}
};
复制

5896. 股票价格波动


【题目描述】

初始化的数据结构:
class StockPrice {
public:
StockPrice() {


}

void update(int timestamp, int price) {


}

int current() {


}

int maximum() {


}

int minimum() {


}
};


/**
* Your StockPrice object will be instantiated and called as such:
* StockPrice* obj = new StockPrice();
* obj->update(timestamp,price);
* int param_2 = obj->current();
* int param_3 = obj->maximum();
* int param_4 = obj->minimum();
*/
复制

【思路】

快速根据key来更新value,要求key与value有准确的映射关系,而由于key取值较大,数组不太合适,可以用哈希表来实现。快速获取最大最小值,这里我使用的是两个堆,一个大顶堆,一个小顶堆。这个思路还有一个问题,如何准确地修改某个元素,修改完还要维护大/小顶堆的性质,同时还要均衡时间复杂度。此时如果堆中仅保存price这一个关键字就不太好做了,需要同时存上timestamp和price构成一个pair。插入或者修改元素时只管插入,只是在查询最大最小price时,若堆顶元素的timestamp与哈希表里的该timestamp对应的不是同一个price,说明该timestamp是被修改过的,原来的最小/大值已被修改了,删掉堆顶元素,重新找下一个值就行。复杂度o(nlogn)。

前面有过几次这类的题,我都用堆解决的,这题也是首先想到的就是堆。评论区也有同学粘了个multiset的代码,set可以精准查找然后删除,底层由红黑树实现,整体时间复杂度也是o(nlogn),代码更精简美,思路也更容易想通,代码我写了一遍,学习一哈。


【代码】
typedef pair<int,int> pii;


class StockPrice {
public:
unordered_map<int , int> record;
priority_queue<pii , vector<pii> , less<pii> > maxp;
priority_queue<pii , vector<pii> , greater<pii> > minp;
int cur;
StockPrice() {
cur = -1;
}

void update(int timestamp, int price) {
if(timestamp > cur)
cur = timestamp;
record[timestamp] = price;
maxp.push({price , timestamp});
minp.push({price , timestamp});
}

int current() {
return record[cur];
}

int maximum() {
pii top = maxp.top();
while(record[top.second] != top.first){
maxp.pop();
top = maxp.top();
}
return top.first;
}

int minimum() {
pii top = minp.top();
while(record[top.second] != top.first){
minp.pop();
top = minp.top();
}
return top.first;
}
};
复制
class StockPrice {
public:
unordered_map<int , int> record;
multiset<int> p;
int cur;
StockPrice() {
cur = -1 ;
}

void update(int timestamp, int price) {
if(record.count(timestamp)){
auto it = p.find(record[timestamp]);
p.erase(it);
}
record[timestamp] = price;
p.insert(price);
cur = max(cur,timestamp);
}

int current() {
return record[cur];
}

int maximum() {
return *prev(p.end());
}

int minimum() {
return *p.begin();
}
};
复制

5897. 将数组分成两个数组并最小化数组和的差


【题目描述】


【思路】

2n个元素分成两个集合,使两个集合的和的差最小,可以认为是给i个元素加上负号,剩下的元素加正号,使2n个元素和最小。可以状态压缩枚举i。但是,2n能到30,状态压缩直接枚举的话,2^30超过了10^9,会超时。可以分成两部分来考虑。前n个元素里选i个加正号,后n个元素里选j个加正号,剩下的2n-i-j个元素都加负号,找和最小的情况。这也就是把2n分成两块来做,分治的思想,看起来,规模只是从30变成15,但指数是爆炸式增长的,这个减小实质上给性能带来很大提高。

操作上,状态压缩模拟前n个元素选取方案,找出前n个元素中任意选i个元素添加正号,剩下元素添负号后所有可能的和,后n个同理。这一步数据处理完成后,对于每个i,再遍历一次,找后n个元素中选n-i个元素添加正号时整体的和的最小绝对值。看到这估计会有点难懂,可以看看代码实现细节,帮助理解。


【代码】
class Solution {
public:
vector<int> sum1[16], sum2[16];
int minimumDifference(vector<int>& nums) {
int n = nums.size() / 2;
int ans = 1e9 + 10;
for(int i = 0; i < ( 1 << n ); i ++ ){
int sum = 0, cnt = 0;
for(int j = 0; j < n; j ++)
{
if(i >> j & 1){
cnt ++;
sum += nums[j];
}else{
sum -= nums[j];
}
}
sum1[cnt].push_back( sum );
}
for(int i = 0; i < 16; i ++)
sort(sum1[i].begin() , sum1[i].end());


for(int i = 0; i < ( 1 << n ); i ++ ){
int sum = 0, cnt = 0;
for(int j = 0; j < n; j ++)
{
if(i >> j & 1){
cnt ++;
sum += nums[n + j];
}else{
sum -= nums[n + j];
}
}
sum2[cnt].push_back( sum );
}
for(int i = 0; i < n; i ++)
sort(sum2[i].begin() , sum2[i].end());

for(int i = 0; i < n; i ++){
for(int j = sum1[i].size()-1 , k = 0; j >= 0; j--){
/* 注意 k 和 j 不能同时从0或者同时从尾开始扫 否则会丢失解*/
while(k < sum2[n - i].size() && sum1[i][j] + sum2[n - i][k] < 0)
k ++;
if(k < sum2[n - i].size())
ans = min(ans , abs(sum1[i][j] + sum2[n - i][k]));
if(k >= 1)
ans = min(ans , abs(sum1[i][j] + sum2[n - i][k - 1]));
}
}
return ans;
}
};
复制

点我可留言噢~
文章转载自fighting小王子,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论