【C/C++】2242. 节点序列的最大得分

280 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情


题目链接:2242. 节点序列的最大得分

题目描述

给你一个 n 个节点的 无向图 ,节点编号为 0 到 n - 1 。

给你一个下标从 0 开始的整数数组 scores ,其中 scores[i] 是第 i 个节点的分数。同时给你一个二维整数数组 edges ,其中 edges[i] = [ai, bi] ,表示节点 aia_i 和 bib_i 之间有一条 无向 边。

一个合法的节点序列如果满足以下条件,我们称它是 合法的 :

  • 序列中每 相邻 节点之间有边相连。
  • 序列中没有节点出现超过一次。

节点序列的分数定义为序列中节点分数之

请你返回一个长度为 4 的合法节点序列的最大分数。如果不存在这样的序列,请你返回 -1 。

提示:

  • n == scores.length
  • 4n51044 \leqslant n \leqslant 5 * 10^4
  • 1scores[i]1081 \leqslant scores[i] \leqslant 10^8
  • 0edges.length51040 \leqslant edges.length \leqslant 5 * 10^4
  • edges[i].length == 2
  • 0ai,bin10 \leqslant a_i, b_i \leqslant n - 1
  • aibia_i ≠ b_i
  • 不会有重边。

示例 1:

ex1new3.png

输入:scores = [5,2,9,8,4], edges = [[0,1],[1,2],[2,3],[0,2],[1,3],[2,4]]
输出:24
解释:上图为输入的图,节点序列为 [0,1,2,3] 。
节点序列的分数为 5 + 2 + 9 + 8 = 24 。
观察可知,没有其他节点序列得分和超过 24 。
注意节点序列 [3,1,2,0][1,0,2,3] 也是合法的,且分数为 24 。
序列 [0,3,2,4] 不是合法的,因为没有边连接节点 03

示例 2:

ex2.png

输入:scores = [9,20,6,4,11,12], edges = [[0,3],[5,3],[2,4],[1,3]]
输出:-1
解释:上图为输入的图。
没有长度为 4 的合法序列,所以我们返回 -1

整理题意

题目给出一个 无向图 ,图中每个节点对应一个节点分数,scores[i] 表示节点 i 所对应的节点分数,要求在图中找到长度为 4 的节点链,每个节点不同且使得节点分数之和最大。返回最大节点分数和,如果不存在长度为 4 的节点链返回 -1

解题思路分析

习惯性动作,首先观察题目数据范围:

  • 点和边的数量都是在 5e4 以内;(数量较大,暴力会 TLE 超时)
  • 节点分数在 1e8 以内。(长度为 4 的节点链最大节点分数和为 4e8,没有超过 int 数据范围)

由于点和边的数量很大,暴力搜索会超时,考虑枚举:

  1. 由于长度为 4 ,有 4 个节点、3 条边,如果枚举点的话比较麻烦,这里考虑枚举边。
  2. 枚举中间的边相比于枚举端点两边的边效率更高,所以我们 枚举中间的边,贪心构造剩余的两条边即可

2242. 节点序列的最大得分.jpg

具体实现

由于我们的 核心思想枚举中间的边,贪心构造剩余的两条边 ,既然是贪心,那么就要对边所连接的节点进行排序,按照节点分数进行排序:

  • 选取与节点 x 相连的分数最大节点 a(除节点 y,也就是 a ≠ y
  • 选取与节点 y 相连的分数最大节点 b(除节点 x,也就是 b ≠ x
  • 同时需要保证节点 a ≠ b,如果 a = b,我们需要贪心选择节点分数次大的。
  • 当枚举的边能够构造成 a - x - y - b 的序列时,我们记录节点分数和的最大值,每次取 max 即可。

复杂度分析

  • 时间复杂度:O(nlog2m+m)O(n\log_2 m + m)n 为节点数量,m 为边的数量,对于 n 个节点的每条边所连接的节点进行排序需要 O(nlog2m)O(n\log_2 m),枚举每条边需要 O(m)O(m)
  • 空间复杂度:O(m)O(m)m 为边的数量,仅需建图空间。

代码实现

class Solution {
public:
    int maximumScore(vector<int>& scores, vector<vector<int>>& edges) {
        int n = scores.size();
        //建图 G[x][i] = y 表示 x和y有一条边
        vector<vector<int>> G(n);
        for(int i = 0; i < n; i++) G[i].clear();
        int m = edges.size();
        for(int i = 0; i < m; i++){
            G[edges[i][0]].push_back(edges[i][1]);
            G[edges[i][1]].push_back(edges[i][0]);
        }
        //对边按照scores从高到低排序
        for(int i = 0; i < n; i++){
            sort(G[i].begin(), G[i].end(), [&](int a, int b){
                return scores[a] > scores[b];
            });
        }
        int ans = -1;
        //枚举中间边edges[i][0]和edges[i][1]之间的边
        for(int i = 0; i < m; i++){
            int x = edges[i][0];
            int y = edges[i][1];
            //长度为4的序列:a-x-y-b
            vector<int> a, b;
            a.clear();
            b.clear();
            //将备选的a和b放入数组
            int Size = G[x].size();
            for(int i = 0; i < Size && a.size() < 2; i++){
                //不能与 y 点重复
                if(G[x][i] != y) a.push_back(G[x][i]);             
            }
            Size = G[y].size();
            for(int i = 0; i < Size && b.size() < 2; i++){
                //不能与 x 点重复
                if(G[y][i] != x) b.push_back(G[y][i]);
            }
            //如果其中a或者b为空表示一段没有边,无法构成长度为4的序列
            if(a.empty() || b.empty()) continue;
            else{
                int len = scores[x] + scores[y];
                //如果两个首选(因为排过序,所以一定是最大的)
                if(a[0] != b[0]) ans = max(ans, scores[a[0]] + scores[b[0]] + len);
                else{
                    //如果 a[0] == b[0] 那么就看看次选
                    if(a.size() > 1) ans = max(ans, scores[a[1]] + scores[b[0]] + len);
                    if(b.size() > 1) ans = max(ans, scores[a[0]] + scores[b[1]] + len);
                }
            }
        }
        return ans;
    }
};

总结

本题的核心思路是 枚举中间的边,贪心构造剩余的两条边 ,贪心构造的过程中需要选取备选节点,在枚举和构造的过程中注意判断重复节点即可。由于题目要求寻找的序列长度为 4,我们以这个作为突破点,巧妙选取枚举对象,贪心构造序列。


结束语

人生如棋,不能只看一时的得失,更要有长远的考量。凡事预则立,不预则废,成功没有什么太过复杂的思维方式,不过是凡事多想一步,遇事淡定处理。定好长期目标,走好当下的每一步,相信收获就在不远的前方。