Leetcode 第 128 场双周赛题解
Leetcode 第 128 场双周赛题解
题目1:3110. 字符串的分数
思路
模拟。
代码
/*
* @lc app=leetcode.cn id=3110 lang=cpp
*
* [3110] 字符串的分数
*/
// @lc code=start
class Solution
{
public:
int scoreOfString(string s)
{
int n = s.length();
int sum = 0;
for (int i = 0; i < n - 1; i++)
sum += abs(s[i + 1] - s[i]);
return sum;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是字符串 s 的长度。
空间复杂度:O(1)。
题目2:3111. 覆盖所有点的最少矩形数目
思路
降维:由于矩形的高没有限制,所以我们只需考虑 points 的横坐标。
算法:
- 将数组 points 按照横坐标从小到大的顺序排序。
- 为方便计算,假设第一个矩形左边还有一个矩形,初始化 right = −1,因为所有横坐标都是非负数。
- 遍历横坐标 x=points[i][0],如果 x > right,说明需要新建一个以 (x, 0) 为左下角的矩形,ans 加一。基于贪心的思想,矩形越大越能包含更多的点,更新右边界 right = point[0] + w。
- 最后,返回 ans。
代码
/*
* @lc app=leetcode.cn id=3111 lang=cpp
*
* [3111] 覆盖所有点的最少矩形数目
*/
// @lc code=start
// 降维 + 排序 + 贪心
class Solution
{
public:
int minRectanglesToCoverPoints(vector<vector<int>> &points, int w)
{
// 由于矩形的高没有限制,所以我们只需考虑 points 的横坐标。
sort(points.begin(), points.end(),
[&](const vector<int> &p1, const vector<int> &p2)
{ return p1[0] < p2[0]; });
int ans = 0;
int right = -1; // 矩形的右边界
for (vector<int> &point : points)
{
if (point[0] > right) // 当前点的横坐标超过了矩形的右边界
{
ans++;
// 以 x=point[0] 为左边界新建一个矩形,为了包含最多点,右边界为 point[0] + w
right = point[0] + w;
}
}
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组 points 的长度。
空间复杂度:O(1)。
题目3:3112. 访问消失节点的最少时间
思路
对于本题,answer 几乎就是 dist 数组。只需要在 Dijkstra 算法的基础上,添加一处判断逻辑,在更新最短路之前,如果最短路长度 ≥disappear[i] 则不更新。
代码
/*
* @lc app=leetcode.cn id=3112 lang=cpp
*
* [3112] 访问消失节点的最少时间
*/
// @lc code=start
// 堆优化 Dijkstra(适用于稀疏图)
class Solution
{
private:
const int inf = INT_MAX / 2;
public:
vector<int> minimumTime(int n, vector<vector<int>> &edges, vector<int> &disappear)
{
vector<vector<pair<int, int>>> g(n); // 邻接表
for (auto &edge : edges)
{
int u = edge[0], v = edge[1], w = edge[2];
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}
// dist[i] 表示点 k 到其他节点的最短距离
vector<int> dist(n, inf);
dist[0] = 0;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
pq.emplace(0, 0);
while (!pq.empty())
{
auto [dx, x] = pq.top();
pq.pop();
if (dx > dist[x])
{ // x 之前出堆过
continue;
}
for (auto &[y, d] : g[x])
{
int new_dist = dx + d;
if (new_dist < disappear[y] && new_dist < dist[y])
{
dist[y] = new_dist; // 更新 x 的邻居的最短路
pq.emplace(new_dist, y);
}
}
}
for (int &d : dist)
if (d == inf)
d = -1;
return dist;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n+mlogm),其中 m 是数组 edges 的长度。
空间复杂度:O(n+m),其中 m 是数组 edges 的长度。
题目4:3113. 边界元素是最大值的子数组数目
思路
例如 nums=[4,3,1,2,1],在从左到右遍历的过程中,由于 2 的出现,左边的 1 永远不可能与右边的 1 组成一个题目要求的子数组。所以当遍历到 2 时,左边的 1 就是无用数据了,可以清除。清除后我们会得到一个从左到右递减的数据结构:单调栈。
算法:
- 初始化答案等于 n,因为每个元素可以单独组成一个长为 1 的子数组,满足题目要求。
- 维护一个底大顶小的单调栈,记录元素及其出现次数。
- 从左到右遍历 nums,设当前元素为 x。只要 x 大于栈顶,就把栈顶出栈。如果 x 小于栈顶,把 x 及其出现次数 1 入栈。如果 x 等于栈顶,设栈顶记录的出现次数为 cnt,那么 x 可以和左边 cnt 个 x 组成 cnt 个满足要求的子数组,把答案增加 cnt,然后把 cnt 加一。
- 返回答案。
代码实现时,可以往栈底加入一个无穷大哨兵,从而简化判断逻辑。
代码
/*
* @lc app=leetcode.cn id=3113 lang=cpp
*
* [3113] 边界元素是最大值的子数组数目
*/
// @lc code=start
class Solution
{
public:
long long numberOfSubarrays(vector<int> &nums)
{
long long ans = 0LL;
ans += nums.size(); // 每个单独的元素都是边界元素是最大值的子数组
stack<pair<int, int>> stk;
// 无穷大哨兵,不可能被弹出栈
stk.push(make_pair(INT_MAX, 0));
// 遍历
for (int &x : nums)
{
// 比 x 小的元素都不可能构成边界
while (x > stk.top().first)
stk.pop();
if (x == stk.top().first)
{
ans += stk.top().second;
stk.top().second++;
}
else
stk.push(make_pair(x, 1));
}
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(n),其中 n 是数组 nums 的长度。