LeetCode周赛322,图论大赛,AC之后真的酣畅淋漓!

大家好,我是梁唐。

今天是周一,我们照惯例来看看昨天的LeetCode周赛。

昨天是LeetCode周赛第322场,由阿里巴巴质量创新大会赞助。前5名的同学可以获得精美的小礼品……

总体上来说这一场比赛的题目质量不错,比之前手速场要好不少,有一定难度,比较锻炼人。

回环句

句子 是由单个空格分隔的一组单词,且不含前导或尾随空格。

  • 例如,"Hello World""HELLO""hello world hello world" 都是符合要求的句子。

单词 由大写和小写英文字母组成。且大写和小写字母会视作不同字符。

如果句子满足下述全部条件,则认为它是一个 回环句

  • 单词的最后一个字符和下一个单词的第一个字符相等。
  • 最后一个单词的最后一个字符和第一个单词的第一个字符相等。

例如,"leetcode exercises sound delightful""eetcode""leetcode eats soul" 都是回环句。然而,"Leetcode is cool""happy Leetcode""Leetcode""I like Leetcode" 是回环句。

给你一个字符串 sentence ,请你判断它是不是一个回环句。如果是,返回 true ;否则,返回 false

题解

水题,为了操作字符串方便,这里使用了Python。

class Solution:
    def isCircularSentence(self, sentence: str) -> bool:
        words = sentence.split(' ')
        n = len(words)
        for i in range(n):
            nxt = (i+1) % n
            if words[i][-1] != words[nxt][0]:
                return False
        return True
复制代码

划分技能点相等的团队

给你一个正整数数组 skill ,数组长度为 偶数 n ,其中 skill[i] 表示第 i 个玩家的技能点。将所有玩家分成 n / 22 人团队,使每一个团队的技能点之和 相等

团队的 化学反应 等于团队中玩家的技能点 乘积

返回所有团队的 化学反应 之和,如果无法使每个团队的技能点之和相等,则返回 -1

题解

题目要求所有队伍技能点数之和相等,所以我们可以事先算出满足条件时每一对队伍的技能点数之和,接着维护每个数字出现的次数。

如果出现不能整除,或者是不能组到n/2组队伍时,说明无解,返回-1,否则累加求和。

class Solution {
public:
    long long dividePlayers(vector<int>& skill) {
        long long tot = 0;
        int n = skill.size();
        map<int, int> mp;
        // 统计每个数字出现的次数
        for (auto x: skill) {
            tot += x;
            mp[x] += 1;
        }
        n /= 2;
        // 如果不能整除,说明无解
        if (tot % n > 0) return -1;
        tot /= n;
        long long ret = 0;
        for (auto x: skill) {
            // 如果当前元素已经在之前组成队了,跳过
            if (mp[x] == 0) continue;
            // 如果符合能组队的元素存在,则组队
            if (mp.count(tot - x) && mp[tot-x] > 0) {
                mp[x] -= 1;
                mp[tot-x] -= 1;
                ret += (long long)x * (tot - x);
            // 否则无解
            }else return -1;
        }
        return ret;
    }
};
复制代码

两个城市间路径的最小分数

给你一个正整数 n ,表示总共有 n 个城市,城市从 1n 编号。给你一个二维数组 roads ,其中 roads[i] = [ai, bi, distancei] 表示城市 aibi 之间有一条 双向 道路,道路距离为 distancei 。城市构成的图不一定是连通的。

两个城市之间一条路径的 分数 定义为这条路径中道路的 最小 距离。

城市 1 和城市 n 之间的所有路径的 最小 分数。

注意:

  • 一条路径指的是两个城市之间的道路序列。
  • 一条路径可以 多次 包含同一条道路,你也可以沿着路径多次到达城市 1 和城市 n
  • 测试数据保证城市 1 和城市n 之间 至少 有一条路径。

题解

最短路问题的变种,题目已经给定了起点和终点,求中间出现的最短边。

我们用dis[i] 维护从1出发到达i点经过的最短边。初始化时将dis数组全部设为无穷大,使用队列来维护所有可能更新其他点的点。初始时队列中只有1,接着我们从1出发,遍历所有与1点相连的点进行松弛操作。假设当前遍历的点是uu->v的边小于dis[v]。也就是说u->v这条边能够更新dis[v],连带着就有可能更新所有与v相连的点,所以我们将v压入队列。

当不存在可以松弛的可能性时,遍历结束,返回dis[n]即是答案。本质上就是最短路问题,连代码都几乎完全一样。

class Solution {
public:
    int minScore(int n, vector<vector<int>>& roads) {
        typedef pair<int, int> pii;
        vector<vector<pii>> graph(n+2, vector<pii>());
		
        // 使用连接表建图
        for (auto vt: roads) {
            int u = vt[0], v = vt[1], l = vt[2];
            graph[u].push_back(make_pair(v, l));
            graph[v].push_back(make_pair(u, l));
        }

        vector<int> dis(n+2, 0x3f3f3f3f);
        queue<int> que;
        que.push(1);
        // 使用bfs进行松弛操作,spfa算法的核心
        while (!que.empty()) {
            int u = que.front();
            que.pop();
            for (auto vt: graph[u]) {
                int v = vt.first, l = vt.second;
                if (min(dis[u], l) < dis[v]) {
                    dis[v] = min(dis[u], l);
                    que.push(v);
                }
            }
        }
        return dis[n];
    }
};
复制代码

将节点分成尽可能多的组

给你一个正整数 n ,表示一个 无向 图中的节点数目,节点编号从 1n

同时给你一个二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示节点 aibi 之间有一条 双向 边。注意给定的图可能是不连通的。

请你将图划分为 m 个组(编号从 1 开始),满足以下要求:

  • 图中每个节点都只属于一个组。
  • 图中每条边连接的两个点 [ai, bi] ,如果 ai 属于编号为 x 的组,bi 属于编号为 y 的组,那么 |y - x| = 1

请你返回最多可以将节点分为多少个组(也就是最大的 m )。如果没办法在给定条件下分组,请你返回 -1

题解

首先, 题目当中说了图不一定连通,也就是说这n个点可能组成若干个连通分量,很容易想到,由于题目要求分组的数量尽量大,我们可以把各个连通分量平铺摆放,这样的分组数量最大,此时的答案为各个连通分量分组数之和。

考虑一个连通分量时,我们可以转换思路,假设有一个外力施加在连通分量的某一个点上,将整个图拎起来。图拎起来之后,我们令这个被拎起来的点是u,我们把其余各点到u的距离看做是深度。那么很容易想到,深度相同的点应该属于同一个分组。分组数即到u距离最远的点和u的距离+1。

从这个结论出发,又可以想到,选择不同的点拎起来会得到不同的结果,并且可能会出现违反题意的情况,我们需要加以判断。

由于点的数量很少,最多只有500,我们可以枚举连通分量中每个点都被拎起来的情况,从中选择符合题意且最大的作为最终结果。和上一题一样,我们可以利用最短路算法来求连通分量中每个点到u的距离。维护连通分量中的所有点,以及所有边,来判断是否符合题意。

更多细节可以查看代码。

class Solution {
public:
    int magnificentSets(int n, vector<vector<int>>& edges) {
        vector<int> graph[n+1];

        // 邻接表建图
        for (auto &vt : edges) {
            int u = vt[0], v = vt[1];
            graph[u].push_back(v);
            graph[v].push_back(u);
        }

        // 存储每个连通分量的结果
        map<int, int> mp;

        auto query = [&](int u) {
            queue<int> que;
            que.push(u);
            // dis[i]为i到u的最短距离
            vector<int> dis(n+1, 0x3f3f3f3f);
            dis[u] = 0;
            // tmp存储连通分量中的最大距离
            int tmp = 0;
            // cur维护连通分量中序号最小的点,方便维护连通分量中的最大值
            int cur = u;
            // 存储连通分量中的所有节点
            set<int> comp;
            comp.insert(u);
            while (!que.empty()) {
                int u = que.front(); que.pop();
                comp.insert(u);
                cur = min(cur, u);
                for (auto v: graph[u]) {
                    if (dis[v] > dis[u] + 1) {
                        dis[v] = dis[u] + 1;
                        tmp = max(tmp, dis[v]);
                        que.push(v);
                    }
                }
            }

            // 初始化mp[cur]=-1,即无解
            if (!mp.count(cur)) mp[cur] = -1;
            // 枚举所有边
            for (auto u: comp) {
                for (auto v : graph[u]) {
                    // 如果相连的两点序号不是相差了1,说明不成立
                    if (abs(dis[u] - dis[v]) != 1) return ;
                }
            }

            // 如果成立,记录tmp + 1
            mp[cur] = max(mp[cur], tmp + 1);
        };

        int ret = 0;
        for (int i = 1; i <= n; i++) {
            query(i);
        }

        for (auto it : mp) {
            if (it.second < 0) return -1;
            ret += it.second;
        }

        return ret;
    }
};
复制代码

这一场涉及到了一些图论的知识,对于图论不太熟悉的小伙伴做起来可能会比较吃力,尤其是最后一题,要想明白题目中的所有隐藏条件,并且理清思路写出代码还是挺难的。

不过正是因为困难,所以才有成就感,绞尽脑汁之后的AC无疑是最好的回报。