5440. 数组异或操作
知识点:暴力枚举
感觉是有啥规律,但没发现~ 所以还是直接暴力枚举吧
class Solution {
public:
int xorOperation(int n, int start) {
int anw = 0;
for(int i = 0; i < n; i++) {
anw ^= (start + 2*i);
}
return anw;
}
};
5441. 保证文件名唯一
知识点:正则表达式,hashmap
文件命名规则:
- 如果 filename 没有出现过,则直接使用该名称。
- 如果 filename 出现过,则在 filename 后添加一个尽可能小的正整数,使得新的名称不重复。
有两个比较烦人的地方:
- 如果给出的 filename 已经出现且带有后缀,那么要在后面加新的后缀而不是直接修改。比如已有 f,f(1),再输入f(1) 时要变为 f(1)(1),而不能直接修改为 f(2)。
- 因为 filename 本身有可能带有后缀,所以已有后缀有可能是不连续的,添加时需注意重复。比如已有 f,f(2),f(3),再次输入f,f 时要分别更新为 f(1),f(4)。
为了解决上述问题,先明确几个概念:
- filename:文件名。
- name:去除数字后缀之后的前缀部分。如果 filename 没有后缀,那么 name 和 filename 相同。
- id:后缀中的数字。如果 filename 没有后缀,那么 id = 0。
还需要几个容器:
- unordered_set<string> mark:存储所有出现过的 filename。
- unordered_map<string, int> next:prefix 下一个可以用的id。
- unordered_map<string, unordered_set<int>> diff:prefix 已经出现过的id。
接下来枚举 names 即可:
- 如果 names[i] 不在 mark 中,那么直接使用 names[i] 作为 filename。
- 如果 names[i] 在 mark 中,那么将 names[i] 作为 prefix 从 next 容器中获取下一个可用的 id,拼接后获得可用的 filename (为了解决第一个坑)
- 用 filename 更新 mark。
- 用 names[i] 的 prefix 与 id 更新 next 和 diff。
- 用 filename 的 prefix 与 id 更新 next 和 diff。(两次更新是为了解决第二个坑)
class Solution {
unordered_map<string, unordered_set<int>> diff;
unordered_map<string, int> next;
unordered_set<string> mark;
int getNext(const string &name) {
auto it = next.find(name);
if(it == next.end()) {
return 0;
}
return it->second;
}
void update(const string &name, int id) {
diff[name].insert(id);
auto it = next.find(name);
if(it == next.end()) {
it = next.insert(make_pair(name, 0)).first;
}
while(diff[name].find(it->second) != diff[name].end()) {
it->second ++;
}
}
public:
vector<string> getFolderNames(vector<string>& names) {
vector<string> anw;
regex re("(.*)\\(([0-9]+)\\)$");
smatch res;
for(const auto &file : names) {
cout << file << endl;
bool found = regex_search(file, res, re);
string name;
int id;
if(found) {
name = res.str(1);
sscanf(res.str(2).c_str(), "%d", &id);
} else {
name = file;
id = 0;
}
if(mark.find(file) == mark.end()) {
anw.push_back(file);
mark.insert(file);
update(name, id);
update(file, 0);
} else {
int next = getNext(file);
char buff[40];
sprintf(buff, "%s(%d)", file.c_str(), next);
string tmp = buff;
update(tmp, 0);
update(file, next);
anw.push_back(tmp);
mark.insert(tmp);
}
}
return anw;
}
};
5442. 避免洪水泛滥
知识点:lower_bound,贪心
出题人又开了上帝视角,可以在还没到第 i 天时就知道第 i 天下不下雨,以及下在哪里~
根据题意,当第 i 天下雨时要保证 rains[i] 是干的,需要知道 rains[i] 在第 i 天之前是否下过雨,以及是否有机会将其抽干。
针对第一点,可以在枚举过程中使用 hashmap 记录每个湖泊的最后一次下雨的时间。
针对第二点,可以在枚举过程中使用 set 记录没有下雨的天数。
当枚举到 rains[i] 时:
- 如果 rains[i] 不下雨,则更新 set,set.insert(i)。
- 如果 rains[i]下雨:
- 如果 rains[i] 不在 hashmap 中,则直接更新 hashmap 即可,hashmap[rains[i]] = i。
- 如果 rains[i] 在 hashmap 中,则从 set 中选取一个不小于 hashmap[rains[i]] 的最小的正整数 x,说明可以在第 x 天时将 rains[i] 抽干。
然后更新, set.erase(x),hashmap[rains[i]] = i。 - 如果不存在这样的 x,说明无解。
为什么要选不小于 hashmap[rains[i]] 的最小正整数 x ?
- 如果 x 小于 hashmao[rains[i]],显然没有意义。
- 设 hashmap[rains[i]] 为 p,然后有 x,y 天可以抽水,且 p < x < y。如果 选择 y 抽干 p,可能会造成 [x+1, y-1] 内需要抽干的湖泊发生洪水,所以选择较小的 x 是一种更优的策略。
class Solution {
set<int> free;
unordered_map<int, int> mark;
map<int, int> anw;
public:
vector<int> avoidFlood(vector<int>& rains) {
for(int i = 0; i < rains.size(); i++) {
if(rains[i] == 0) {
free.insert(i);
} else {
if(mark.find(rains[i]) != mark.end()) {
auto it = free.lower_bound(mark[rains[i]]);
if(it == free.end()) {
return vector<int>{};
}
anw.insert(make_pair(*it, rains[i]));
free.erase(it);
}
anw.insert(make_pair(i, -1));
mark[rains[i]] = i;
}
}
vector<int> res;
for(int i = 0; i < rains.size(); i++) {
if(anw[i] == 0) {
if(rains[i] == 0) {
anw[i] = 1;
} else {
anw[i] = -1;
}
}
res.push_back(anw[i]);
}
return res;
}
};
5443. 找到最小生成树里的关键边和伪关键边
知识点:最小生成树,并查集
特别的,如果给出的图不存在MST,那么答案为空。下面仅讨论存在MST的情况。
简介构造MST 的 Kruskal算法:
算法分析:
Kruskal算法是将一个连通块当做一个集合。Kruskal首先将所有的边按从小到大顺序排序(一般使用快排),并认为每一个点都是孤立的,分属于n个独立的集合。
然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合(这就是一条边);
如果这条边连接的两个点属于同一集合(说明这条边找过了),就跳过。直到选取了n-1条边为止。
思路讲解:
Kruskal算法每次都选择一条最小的,且能合并两个不同集合的边,一张n个点的图总共选取n-1次边。因为每次我们选的都是最小的边,所以最后的生成树一定是最小生成树。每次我们选的边都能够合并两个集合,最后n个点一定会合并成一个集合。通过这样的贪心策略,Kruskal算法就能得到一棵有n-1条边,连接着n个点的最小生成树。
解题思路
先明确三个概念:
- 关键边:在所有的MST都出现了的边。
- 伪关键边:出现在部分MST中的边。
- 冗余边:在所有MST中都未出现的边。
由定义可知,三者的交集为空,三者的并集就是图的所有边。
设MST的权值和为 minVal。
如果删除一个边之后,其MST不存在或者权值和发生了变化,那么这条边肯定是关键边。
如果先将一条边加入到MST中,然后再按照MST算法处理剩余的边,如果权值和发生变化,那么这条边就是冗余边。
既不是关键边也不是冗余边的那些边就是伪关键边咯。
class Solution {
struct Edge {
int u, v, w, p;
bool operator < (const Edge &rhs) const {
if(p == rhs.p) {
return w < rhs.w;
}
return p < rhs.p;
}
};
int find(int *fa, int u) {
int t = u;
while(t != fa[t]) {
t = fa[t];
}
while(u != fa[u]) {
int tmp = fa[u];
fa[u] = t;
u = tmp;
}
return t;
}
bool merge(int *fa, int u, int v) {
int fu = find(fa, u);
int fv = find(fa, v);
if(fu == fv) {
return false;
}
fa[fu] = fv;
return true;
}
int create(int n, vector<Edge> &edges) {
sort(edges.begin(), edges.end());
int fa[100];
for(int i = 0; i < n; i++) {
fa[i] = i;
}
int sum = 0;
int cnt = n;
for(const auto &edge : edges) {
if(merge(fa, edge.u, edge.v)) {
cnt--;
sum += edge.w;
}
}
if(cnt == 1) {
return sum;
}
return -1;
}
public:
vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
vector<Edge> tmpEdges;
for(int i = 0; i < edges.size(); i++) {
tmpEdges.push_back(Edge{edges[i][0], edges[i][1], edges[i][2], 0});
}
int minVal = create(n, tmpEdges);
if(minVal == -1) {
return vector<vector<int>>{};
}
vector<int> keyEdges;
unordered_set<int> edgeMark;
for(int i = 0; i < edges.size(); i++) {
vector<Edge> vec;
for(int j = 0; j < edges.size(); j++) {
if(i == j) {
continue;
}
vec.push_back(Edge{edges[j][0], edges[j][1], edges[j][2], 0});
}
int tmpVal = create(n, vec);
if(tmpVal != minVal) {
keyEdges.push_back(i);
edgeMark.insert(i);
}
}
unordered_set<int> garbageEdges;
for(int i = 0; i < edges.size(); i++) {
vector<Edge> vec;
for(int j = 0; j < edges.size(); j++) {
if(i == j) {
vec.push_back(Edge{edges[j][0], edges[j][1], edges[j][2], 0});
} else {
vec.push_back(Edge{edges[j][0], edges[j][1], edges[j][2], 1});
}
}
int tmpVal = create(n, vec);
if(tmpVal > minVal) {
edgeMark.insert(i);
}
}
vector<int> falseKeyEdges;
for(int i = 0; i < edges.size(); i++) {
if(edgeMark.find(i) == edgeMark.end()) {
falseKeyEdges.push_back(i);
}
}
return vector<vector<int>> {keyEdges, falseKeyEdges};
}
};