LeetCode第 194 场周赛题解

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};
    }
};

猜你喜欢

转载自blog.csdn.net/Time_Limit/article/details/106886547
今日推荐