LeetCode周赛194总结

这次来参加了一下194周赛,做完前两道,后面第三道思路不太对,写错了,就差了一个函数调用。。最后一道是一个prim/kruskal算法求解最小生成树的问题,这两个算法之前也没实现过,于是就不太会做了,下来总结一下这次的题解,互相学习吧,期待下次周赛的进步。

1.数组异或操作

题目:给你两个整数,nstart

数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == nums.length

请返回 nums 中所有元素按位异或(XOR)后得到的结果。

题解:打卡题,按照题目走就完事了。直接异或。

实现

class Solution {
public:
    int xorOperation(int n, int start) {
        int ans=0;
        for (int i=0;i<n;i++) {
            ans^=(2*i)+start;
        }
        return ans;
    }
};
 

2.保证文件名唯一

题目:给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。

由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数

返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。

题解

这道题真的很简单,事后发现很多人卡在了这一道题上,还好当时我很快就做出来了,算是一点小小激励。

这道题直接暴力,使用一个map就搞定了,只需要当前元素塞入map中,判断当前是否在map中,在map中就先拼接一个文件名,注意这里是将map的val进行拼接,这样可以记录遍历过程中的文件重复次数,最后一点就是把新得到的文件塞入map中,确保当names数组中有同名的进行处理。

实现

class Solution {
public:
    vector<string> getFolderNames(vector<string>& names) {
        int n = names.size();
        map<string,int> m;
        vector<string> ans;
        for (auto elem : names) {
            if(m.count(elem)==0) {
                ans.push_back(elem);
                m[elem]++;
            } else {
                int i = m[elem];
                m[elem]++;
                string cur = elem+"("+to_string(i)+")";
                while(m.count(cur)>0) {
                    cur = elem+"("+to_string(i++)+")";
                }
                m[cur]=1; // 把当前放入map中
                ans.push_back(cur);
            }
        }  
        return ans;
    }
};

3.避免洪水泛滥

题目:你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨的时候,如果第 n 个湖泊是空的,那么它就会装满水,否则这个湖泊会发生洪水。你的目标是避免任意一个湖泊发生洪水。

给你一个整数数组 rains ,其中:

  • rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。

  • rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。

请返回一个数组 ans ,满足:

  • ans.length == rains.length

  • 如果 rains[i] > 0 ,那么ans[i] == -1

  • 如果 rains[i] == 0ans[i] 是你第 i 天选择抽干的湖泊。

如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组

请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生(详情请看示例 4)。

题解: 用不下雨去抽干最近一次重复下雨的湖泊

1) 不下雨判断 直接判断是否是0

2) 重复下雨的湖泊,使用map记录

实现

class Solution {
public:
    vector<int> avoidFlood(vector<int>& rains) {
        // 用不下雨去抽干最近一次重复下雨的湖泊 

        // 1) 不下雨判断 直接判断是否是0
        // 2) 重复下雨的湖泊,使用map记录
        int n = rains.size();
        map<int,int> m; // 下雨的湖泊与天的关系
        set<int> s; // 不下雨的天
        vector<int> res(n,-1); // 存储结果
        for (int i=0;i<n;i++) {
            if (rains[i] == 0) { // 不下雨
                s.insert(i);
            } else {
                if (m.count(rains[i]) == 0) { // 第一次下雨
                    m[rains[i]] = i;   
                } else {
                    auto it = s.lower_bound(m[rains[i]]);  // 找到第一个可以抽干(重复下雨的池子)
                    if (it == s.end()) return vector<int>(); // not found  
                    res[*it] = rains[i]; // 抽干哪个池子
                    m[rains[i]] = i;    // 更新第i天下雨
                    s.erase(it);    // 删除可以抽的那一天
                }
            }
        }
        // 可以抽干的池子比较多,随便填个数据
        for (auto elem : s) res[elem] = 1;
        return res;
    }
};

4.找到最小生成树里的关键边和伪关键边

题目:给你一个 n 个点的带权无向连通图,节点编号为 0n-1 ,同时还有一个数组 edges ,其中 edges[i] = [from``i, toi, weighti] 表示在 fromitoi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。

请你找到给定图中最小生成树的所有关键边和伪关键边。如果最小生成树中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。

请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。

题解:使用kruskal算法得到最小生成树。

  • 关键边:若已有的最小生成树权值与不使用第i条边得到的权重不一样,则i是关键边

  • 伪关键边:若已有的最小生成树权值与不适用第i条边得到的权重一样,则i是伪关键边

实现

class Solution {

private:
    vector<int> uf; // unionfind数组
    
    int find(int x) {  // 查找当前节点的根
        if(x == uf[x]) return x;
        return uf[x] = find(uf[x]);
    }
    void union_ab(int a,int b) { // 将两个节点进行合并
        uf[a] = b;   // a的父亲是b
    }
    int kruskal(int n, vector<vector<int>>& edges, int k, int not_use) {  
        for (int i=0;i<n;i++) uf[i]=i; // 初始化并查集
        int cost = 0;
        // 使用第k条表flag开启 先让第k条边一定被使用
        if (k!=-1 && !not_use) {
            for (auto& e : edges) {
                if (e[3] == k) {    // 第k条边
                    int p1 = find(e[1]); // to
                    int p2 = find(e[2]); // from
                    if (p1!=p2) {
                        cost += e[0]; // 权重
                        union_ab(p1,p2);
                        n--;
                    }
                    break;
                }
            }
        }
        // 拿出每一条边  不使用第k条边的逻辑
        for (auto& e : edges)  {
            if(e[3] == k) {
             continue;   // 不使用第k条边
            } 
            int p1 = find(e[1]); // to
            int p2 = find(e[2]); // from
            if (p1!=p2) {
                cost += e[0]; // 权重
                union_ab(p1,p2);
                n--;
            }
        }
        if (n>1)  return 0x3f3f3f3f;    // n个节点 n-1条边构成最小生成树
        return cost;
    }

public:
    /**
     * 使用kruskal算法得到最小生成树
     * 关键边:若已有的最小生成树权值与不使用第i条边得到的权重不一样,则i是关键边
     * 伪关键边:若已有的最小生成树权值与不适用第i条边得到的权重一样,则i是伪关键边
     *
     * kruskal算法两个关键点:
     * - 边排序,每次选择最短的边
     * - 检测无环,使用unionfind算法
     */
    
    vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
        uf = vector<int>(n); // union find数组初始化
        int m = edges.size();
        // 边排序
        for (int i=0;i<m;i++) {
            // 把边放到vector的开头,方便排序
            swap(edges[i][0],edges[i][2]);
            edges[i].push_back(i);
        }     
        sort(edges.begin(),edges.end());
        // 求出最小生成树的权值
        int cost = kruskal(n, edges, -1, false);   // -1 表示求所有节点所得到的最小生成树
        vector<vector<int>> res(2);
        // 尝试每一条边
        for (int i=0;i<m;i++) {
            if (cost != kruskal(n,edges,i,true)) {   // 已有的最小生成树权值与不使用第i条边的权值不一样,那么就是关键边
                res[0].push_back(i);
            } else if (cost == kruskal(n,edges,i,false)) {
                res[1].push_back(i);
            }
        }
        return res;
    }

};

LeetCode周赛194总结_权重