双周赛 57 题解
四道题都不难,中间两题卡了我比较长时间,幸亏最后一题是个单调栈一眼题,不然就要掉大分了
知识点:优先队列,差分数组,单调栈
检查是否所有字符出现次数相同
给定一个字符串 \(s\),如果 \(s\) 中所有字符串出现的次数一样,那么我们称之为好字符串,请返回 \(s\) 是否是好字符串
题解
把每一个字符出现的次数用桶维护,扔到集合里面去,利用集合的去重功能,判断集合的 size
是否为 \(1\)
class Solution {
public:
bool areOccurrencesEqual(string s) {
vector<int> a(26);
int n = s.length();
set<int> st;
for (int i = 0; i < n; ++i) a[s[i] - 'a']++;
for (int i = 0; i < 26; ++i)
if (a[i]) st.insert(a[i]);
return st.size() == 1;
}
};
最小未被占据椅子的编号
\(n\) 个人在办聚会,有无限个椅子,每个人有到达时间和离开时间,每个人到达聚会的时候会选择空闲的最小编号的椅子
现在给定 \(n\) 个人的到达时间和离开时间,计算出第 \(t\) 个人坐的椅子的编号
题解
用一个优先队列存储每个椅子的空闲时间和编号,用一个集合存储 \(n\) 个椅子的编号
遍历 \(n\) 个人,根据到达时间和离开时间处理空闲和忙碌的椅子,更新优先队列和集合即可
// cpp
struct node {
int idx, ed;
node() {}
node(int _idx, int _ed):
idx(_idx), ed(_ed) {}
};
bool operator< (const node& x, const node& y) {
if (x.ed == y.ed) return x.idx < y.idx;
return x.ed > y.ed;
}
bool cmp(vector<int> &x, vector<int> &y) {
if (x[0] == y[0]) return x[1] < y[1];
return x[0] < y[0];
}
class Solution {
public:
int smallestChair(vector<vector<int>>& t, int target) {
vector<int> vec;
int n = t.size();
for (int i = 0; i < n; ++i) vec.push_back(i), t[i].push_back(i);
set<int> st(vec.begin(), vec.end());
priority_queue<node> pq;
sort(t.begin(), t.end(), cmp);
int res = 0;
for (int i = 0; i < n; ++i) {
while (!pq.empty()) {
node temp = pq.top();
if (temp.ed <= t[i][0]) pq.pop(), st.insert(temp.idx);
else break;
}
res = *st.begin();
pq.push(node(res, t[i][1]));
st.erase(st.begin());
if (t[i][2] == target) break;
}
return res;
}
};
描述绘画结果
给定 \(n\) 个左闭右开区间 \([l,\ r)\),并给这个区间上每一个值加上 \(w\)
返回操作后的数轴,要求将相同值写成左闭右开区间的形式,值为 \(0\) 的区间跳过
举例来讲,如果 \(1,\ 2,\ 3\) 的值都是 \(12\),并且 \(4\) 的值为 \(13\),那我们可以把其合并成 \([1,\ 4),\ [4,\ 5)\),
注意,对于值相同的区间,如果达到这个和的方式不一样,不能写成一个区间
例如 \([1, 4),\ [4,\ 7)\) 均为 \(12\),但前者为 \(5 + 7\),后者为 \(3 + 9\),此时我们不能将其合并
题解
原题题意比较冗余,也很难抽象题意描述,但是考点很简单:差分数组
差分数组说的是:做 \(n\) 次区间加法操作,可以用离线算法在 \(O(n)\) 时间得到更新后的区间
具体来讲,如果要给区间 \([l,\ r)\) 统一加上 \(w\),只需要在 \(l\) 处加上 \(w\),在 \(r\) 处减去 \(w\),如此进行 \(n\) 次,最后对原数组进行前缀和,便可以得到更新后的数组,这里的「原数组」被称为差分数组
我们对更新后的数组进行划分,将相同的值归纳为一个区间即可
此题在差分数组的基础上多了一个要求:对于值相同的区间,如果达到这个和的方式不一样,不能写成一个区间
这个也比较好处理,利用贪心的思想,继续构造一个差分数组,区间加「区间右端点」的值即可,聪明的你能想明白贪心的正确性吗?
时间复杂度 \(O(n)\)
// cpp
typedef long long LL;
class Solution {
public:
vector<vector<long long>> splitPainting(vector<vector<int>>& seg) {
int n = seg.size();
int maxx = 0;
for (int i = 0; i < n; ++i) {
maxx = max(maxx, seg[i][1]);
seg[i].push_back(i + 1);
}
vector<LL> vec1(maxx + 7);
vector<LL> vec2(maxx + 7);
for (int i = 0; i < n; ++i) {
int a = seg[i][0], b = seg[i][1], c = seg[i][2], d = seg[i][3];
vec1[a] += c, vec1[b] -= c;
vec2[a] += d, vec2[b] -= d;
}
for (int i = 1; i <= maxx; ++i) {
vec1[i] += vec1[i - 1];
vec2[i] += vec2[i - 1];
}
vector<vector<LL>> ans;
int a = 1, b = 1;
for (int i = 1; i <= maxx; ++i) {
if (vec1[i] != vec1[a] || vec1[i] == vec1[a] && vec2[i] != vec2[a]) {
if (!vec1[i - 1]) {a = b = i; continue;}
else {
ans.push_back(vector<LL>{a, i, vec1[i - 1]});
a = b = i;
}
}
}
return ans;
}
};
从队列中可以看到的人数
给定 \(n\) 个人及其身高 \(h_{i}\)
对于两个人 \(i,\ j\),其中 \(i\) 在 \(j\) 左边,如果 \(i,\ j\) 中间的每一个人都没有他们高,那么 \(i\) 可以看到右边的 \(j\)
计算出每个人可以看到的右边的人数
数据规定
\(1\leq n\leq 10^5\)
题解
对于当前的 \(i\) 而言,我们可以计算出他左边第一个比他高的人 \(L\),以及右边第一个比他高的人 \(R\)
如果 \(L\) 存在,那么 \(L\) 可以看到 \(i\)
如果 \(R\) 存在,那么 \(i\) 可以看到 \(R\)
使用单调栈预处理即可,时间复杂度 \(O(n)\)
// cpp
class Solution {
public:
vector<int> canSeePersonsCount(vector<int>& h) {
int n = h.size();
vector<int> rmax(n + 7);
vector<int> lmax(n + 7);
vector<int> ans(n + 7);
vector<int> a(n + 7);
for (int i = 1; i <= n; ++i) a[i] = h[i - 1];
stack<int> mono1, mono2; // 递增
for (int i = 1; i <= n; ++i) {
while (!mono1.empty() && a[mono1.top()] < a[i]) {
rmax[mono1.top()] = i;
mono1.pop();
}
mono1.push(i);
}
for (int i = n; i >= 1; --i) {
while (!mono2.empty() && a[mono2.top()] < a[i]) {
lmax[mono2.top()] = i;
mono2.pop();
}
mono2.push(i);
}
for (int i = 1; i <= n; ++i) {
int L = lmax[i], R = rmax[i];
if (L >= 1 && L <= n) ans[L]++;
if (R >= 1 && R <= n) ans[i]++;
}
return {ans.begin() + 1, ans.begin() + 1 + n};
}
};