作者 | 梁唐
出品 | 公众号:Coder梁(ID:Coder_LT)
大家好,我是梁唐。
今天是周一,我们照惯例来聊聊昨天的LeetCode周赛。
本次的周赛是第323场,是LeetCode官方的学习福利专场。除了前10名可以获得盲盒奖励之外,还有一些排名在幸运位置的同学可以获得LeetBook的奖励。并且,只要通过一题,就可以获得力扣的学习专属福利。属于是普天同庆大礼包了。
可能是为了照顾新入门的同学,本次赛题的质量一般,难度相对来说不算很大。
有老哥表示双周赛的时候还坐牢,没想到周赛的喜提rank1了。对于这样的菊苣,只能送上orz。
给你一个 m x n
大小的矩阵 grid
,由若干正整数组成。
执行下述操作,直到 grid
变为空矩阵:
注意 每执行一次操作,矩阵中列的数据就会减 1 。
返回执行上述操作后的答案。
题目看起来很花哨,但实际上是对每一行进行排序之后,选取每一列的最大值作为得分。最后返回得分之和即可。
class Solution {
public:
int deleteGreatestValue(vector<vector<int>>& grid) {
int n = grid.size();
for (int i = 0; i < n; i++) sort(grid[i].begin(), grid[i].end());
int ret = 0;
int m = grid[0].size();
for (int j = 0; j < m; j++) {
int tmp = 0;
for (int i = 0; i < n; i++) tmp = max(tmp, grid[i][j]);
ret += tmp;
}
return ret;
}
};
给你一个整数数组 nums
。如果 nums
的子序列满足下述条件,则认为该子序列是一个 方波 :
2
,并且返回 nums
中 最长方波 的长度,如果不存在 方波 则返回 -1
。
子序列 也是一个数组,可以由另一个数组删除一些或不删除元素且不改变剩余元素的顺序得到。
题目要求的是一个子序列,并且又说子序列要按照从小到大排序,那么我们可以秒想到,可以先对数组中元素进行排序,这样就可以保证从左到右取出来的子序列就是天然有序的。
其次我们要保证子序列中除了第一个元素之外的每个元素都是之前一个元素的平方,由于我们通过排序保证了我们遍历元素的顺序就是从小到大的,那么我们只需要判断当前元素的完全平方根是否存在在数组中即可。我们使用map
来记录,map[x]
表示以x
结尾的子序列的最长方波,那么map[x] = map[sqrt(x)] + 1
。这个式子写出来是不是很眼熟?
眼熟就对了,其实本质上用到了类似动态规划的思想。这里面的x
和它平方根的关系,就是状态转移的关系。把握住了这一点,代码就很好写了。
class Solution {
public:
int longestSquareStreak(vector<int>& nums) {
sort(nums.begin(), nums.end());
map<int, int> mp;
// 初始化,将所有元素都插入map中
for (auto x: nums) mp[x] = 1;
int ret = 1;
for (auto x: nums) {
// 计算x的平方根
int rt = int(sqrt(x));
// 要记得验算一下平方根是不是成立
if (rt * rt == x && mp.count(rt)) {
int cur = mp[rt] + 1;
mp[x] = cur;
// 更新答案
ret = max(ret, cur);
}
}
return ret > 1 ? ret : -1;
}
};
给你一个整数 n
,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。
请你设计一个具备以下功能的内存分配器:
size
的连续空闲内存单元并赋 id mID
。mID
对应的所有内存单元。注意:
mID
。mID
对应的所有内存单元,即便这些内存单元被分配在不同的块中。实现 Allocator
类:
Allocator(int n)
使用一个大小为 n
的内存数组初始化 Allocator
对象。int allocate(int size, int mID)
找出大小为 size
个连续空闲内存单元且位于 最左侧 的块,分配并赋 id mID
。返回块的第一个下标。如果不存在这样的块,返回 -1
。int free(int mID)
释放 id mID
对应的所有内存单元。返回释放的内存单元数目。class Allocator {
public:
vector<int> al;
int cap;
Allocator(int n) {
al.resize(n);
cap = n;
}
int allocate(int size, int mID) {
int tmp = 0;
for (int i = 0; i < cap; i++) {
if (al[i] == 0) tmp++;
else tmp = 0;
if (tmp == size) {
for (int j = i-size+1; j <= i; j++) al[j] = mID;
return i-size+1;
}
}
return -1;
}
int free(int mID) {
int ret = 0;
for (int i = 0; i < cap; i++) {
if (al[i] == mID) {
ret++;
al[i] = 0;
}
}
return ret;
}
};
/**
* Your Allocator object will be instantiated and called as such:
* Allocator* obj = new Allocator(n);
* int param_1 = obj->allocate(size,mID);
* int param_2 = obj->free(mID);
*/
大家感兴趣也可以想想如果不用暴力的方法,需要用到数据结构来进行维护,难度也会上一个台阶。
给你一个大小为 m x n
的整数矩阵 grid
和一个大小为 k
的数组 queries
。
找出一个大小为 k
的数组 answer
,且满足对于每个整数 queres[i]
,你从矩阵 左上角 单元格开始,重复以下过程:
queries[i]
严格 大于你当前所处位置单元格,如果该单元格是第一次访问,则获得 1 分,并且你可以移动到所有 4
个方向(上、下、左、右)上任一 相邻 单元格。在过程结束后,answer[i]
是你可以获得的最大分数。注意,对于每个查询,你可以访问同一个单元格 多次 。
返回结果数组 answer
。
如果只有一个query,很简单,我们直接从左上角遍历所有连通且小于query的格子的数量。
但本题当中有最多1e4个query,如果我们单独处理显然会超时。所以必须要合并处理,那怎么合并处理呢?
我们可以反向思考,从求每一个query对应的连通块的面积转向求能够覆盖每一个格子所需要的最小代价。之后我们只需要计算每一个query能够满足多少格子即可。query越大能够覆盖的格子也就越多,这两者是递增的,我们没必要一一统计计算。可以一次性全部统计出之后,采用二分的方法来查找。
我们一步一步来看,首先统计出从左上角开始到达每一个格子路径中每个点最大值的最小值,这个值就是覆盖该点的代价。
比如上图当中从左上角到右下角的通路有好几条,一条经过的最大值是7一条是5,显然5更小。我们可以使用spfa算法搞定,代码类似于bfs。
我们使用数组dis
记录每一个点到左上角经过路径中最大格子的最小值,由于grid
是二维的,所以我们需要将二维坐标转化成一维坐标。转化的方式也很简单ni = i * m + j
。
求出了dis
数组之后,我们可以使用一个map
来将dis
中的值进行合并,并计算前缀和。之后在遍历query
数组,利用map
进行二分查找,找到对应的前缀和即是答案,更多细节查看代码。
class Solution {
public:
vector<int> maxPoints(vector<vector<int>>& grid, vector<int>& queries) {
int fx[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
int n = grid.size();
int m = grid[0].size();
// dis存储每个点到左上角路径中经过最大点的最小值
vector<int> dis(n * m, 0x3f3f3f3f);
typedef pair<int, int> pii;
queue<pii> que;
que.push(make_pair(0, 0));
dis[0] = grid[0][0];
// spfa进行松弛
while (!que.empty()) {
auto u = que.front(); que.pop();
int x = u.first, y = u.second;
int iu = x * m + y;
for (int i = 0; i < 4; i++) {
int ux = x + fx[i][0];
int uy = y + fx[i][1];
if (ux < 0 || ux >= n || uy < 0 || uy >= m) continue;
int idx = ux * m + uy;
if (dis[idx] > max(dis[iu], grid[ux][uy])) {
dis[idx] = max(dis[iu], grid[ux][uy]);
que.push(make_pair(ux, uy));
}
}
}
// 存储前缀和
map<int, int> cnt;
for (int i = 0; i < m * n; i++) {
cnt[dis[i]]++;
}
int tmp = 0;
// 计算前缀和
for (auto it: cnt) {
int k = it.first, v = it.second;
cnt[k] += tmp;
tmp = cnt[k];
}
vector<int> ret;
for (auto q: queries) {
// 二分查找
auto it = cnt.lower_bound(q);
if (it == cnt.begin()) ret.push_back(0);
else {
ret.push_back((--it)->second);
}
}
return ret;
}
};
我翻了一下评论区里的题解,不少大佬使用并查集进行解决。可惜他们都言简意赅,我也没能完全搞懂。有兴趣的同学也可以往这个角度思考思考。