Hard

题目描述

有一个由 n 个彩色节点和 m 条边组成的有向图。节点编号从 0n - 1

给你一个字符串 colors,其中 colors[i] 是小写英文字母,表示图中第 i 个节点的颜色(下标从 0 开始)。同时给你一个二维数组 edges,其中 edges[j] = [aj, bj] 表示从节点 aj 到节点 bj 有一条有向边。

图中一条有效路径是一个节点序列 x1 -> x2 -> x3 -> ... -> xk,使得对于每个 1 <= i < k,都存在一条从 xixi+1 的有向边。路径的颜色值是该路径上出现次数最多的颜色的节点数量。

返回给定图中任意有效路径的最大颜色值,如果图中包含环则返回 -1

示例 1:

输入:colors = "abaca", edges = [[0,1],[0,2],[2,3],[3,4]]
输出:3
解释:路径 0 -> 2 -> 3 -> 4 包含 3 个颜色为 "a" 的节点。

示例 2:

输入:colors = "a", edges = [[0,0]]
输出:-1
解释:从 0 到 0 存在一个环。

约束条件:

  • n == colors.length
  • m == edges.length
  • 1 <= n <= 10^5
  • 0 <= m <= 10^5
  • colors 由小写英文字母组成
  • 0 <= aj, bj < n

解题思路

这是一道结合拓扑排序和动态规划的经典题目。

核心思路:

  1. 环检测:使用拓扑排序检测图中是否存在环,如果有环则无法计算最长路径
  2. 动态规划状态定义dp[u][c] 表示从节点 u 开始的路径中,颜色 c 的最大出现次数
  3. 状态转移:对于每个节点 u 和每种颜色 c,通过其所有后继节点更新状态

算法步骤:

  1. 构建邻接表和入度数组
  2. 使用 Kahn 算法进行拓扑排序,同时进行动态规划
  3. 在拓扑排序过程中,对每个节点维护 26 种颜色的最大值
  4. 当处理节点 u 时,如果节点颜色是 c,则 dp[u][c] 至少为 1
  5. 遍历节点 u 的所有邻居 v,更新 dp[v][c] = max(dp[v][c], dp[u][c] + (colors[v] == c ? 1 : 0))

时间复杂度优化: 由于只有 26 种颜色,我们可以为每个节点维护一个大小为 26 的数组,在拓扑排序的过程中逐步更新这些值。

这种方法既保证了环的检测,又能高效地计算出所有路径中各颜色的最大出现次数。

代码实现

class Solution {
public:
    int largestPathValue(string colors, vector<vector<int>>& edges) {
        int n = colors.length();
        vector<vector<int>> graph(n);
        vector<int> indegree(n, 0);
        
        for (auto& edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            indegree[edge[1]]++;
        }
        
        // dp[i][j] represents max count of color j starting from node i
        vector<vector<int>> dp(n, vector<int>(26, 0));
        queue<int> q;
        
        // Initialize nodes with indegree 0
        for (int i = 0; i < n; i++) {
            if (indegree[i] == 0) {
                q.push(i);
                dp[i][colors[i] - 'a'] = 1;
            }
        }
        
        int processed = 0;
        int maxColorValue = 0;
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            processed++;
            
            // Update max color value
            for (int c = 0; c < 26; c++) {
                maxColorValue = max(maxColorValue, dp[u][c]);
            }
            
            for (int v : graph[u]) {
                // Update dp values for neighbor v
                for (int c = 0; c < 26; c++) {
                    int colorCount = dp[u][c] + (colors[v] - 'a' == c ? 1 : 0);
                    dp[v][c] = max(dp[v][c], colorCount);
                }
                
                indegree[v]--;
                if (indegree[v] == 0) {
                    q.push(v);
                }
            }
        }
        
        // Check if there's a cycle
        return processed == n ? maxColorValue : -1;
    }
};
class Solution:
    def largestPathValue(self, colors: str, edges: List[List[int]]) -> int:
        n = len(colors)
        graph = [[] for _ in range(n)]
        indegree = [0] * n
        
        for a, b in edges:
            graph[a].append(b)
            indegree[b] += 1
        
        # dp[i][j] represents max count of color j starting from node i
        dp = [[0] * 26 for _ in range(n)]
        queue = []
        
        # Initialize nodes with indegree 0
        for i in range(n):
            if indegree[i] == 0:
                queue.append(i)
                dp[i][ord(colors[i]) - ord('a')] = 1
        
        processed = 0
        max_color_value = 0
        
        while queue:
            u = queue.pop(0)
            processed += 1
            
            # Update max color value
            max_color_value = max(max_color_value, max(dp[u]))
            
            for v in graph[u]:
                # Update dp values for neighbor v
                for c in range(26):
                    color_count = dp[u][c] + (1 if ord(colors[v]) - ord('a') == c else 0)
                    dp[v][c] = max(dp[v][c], color_count)
                
                indegree[v] -= 1
                if indegree[v] == 0:
                    queue.append(v)
        
        # Check if there's a cycle
        return max_color_value if processed == n else -1
public class Solution {
    public int LargestPathValue(string colors, int[][] edges) {
        int n = colors.Length;
        List<int>[] graph = new List<int>[n];
        int[] indegree = new int[n];
        
        for (int i = 0; i < n; i++) {
            graph[i] = new List<int>();
        }
        
        foreach (int[] edge in edges) {
            graph[edge[0]].Add(edge[1]);
            indegree[edge[1]]++;
        }
        
        // dp[i][j] represents max count of color j starting from node i
        int[,] dp = new int[n, 26];
        Queue<int> queue = new Queue<int>();
        
        // Initialize nodes with indegree 0
        for (int i = 0; i < n; i++) {
            if (indegree[i] == 0) {
                queue.Enqueue(i);
                dp[i, colors[i] - 'a'] = 1;
            }
        }
        
        int processed = 0;
        int maxColorValue = 0;
        
        while (queue.Count > 0) {
            int u = queue.Dequeue();
            processed++;
            
            // Update max color value
            for (int c = 0; c < 26; c++) {
                maxColorValue = Math.Max(maxColorValue, dp[u, c]);
            }
            
            foreach (int v in graph[u]) {
                // Update dp values for neighbor v
                for (int c = 0; c < 26; c++) {
                    int colorCount = dp[u, c] + (colors[v] - 'a' == c ? 1 : 0);
                    dp[v, c] = Math.Max(dp[v, c], colorCount);
                }
                
                indegree[v]--;
                if (indegree[v] == 0) {
                    queue.Enqueue(v);
                }
            }
        }
        
        // Check if there's a cycle
        return processed == n ? maxColorValue : -1;
    }
}
var largestPathValue = function(colors, edges) {
    const n = colors.length;
    const graph = Array(n).fill().map(() => []);
    const indegree = Array(n).fill(0);
    
    for (const [a, b] of edges) {
        graph[a].push(b);
        indegree[b]++;
    }
    
    // dp[i][j] represents max count of color j starting from node i
    const dp = Array(n).fill().map(() => Array(26).fill(0));
    const queue = [];
    
    // Initialize nodes with indegree 0
    for (let i = 0; i < n; i++) {
        if (indegree[i]

复杂度分析

复杂度分析
时间复杂度O(V + E) × 26 = O(26(V + E)),其中 V 是节点数,E 是边数。拓扑排序需要 O(V + E),每个节点需要更新 26 种颜色的状态
空间复杂度O(V × 26 + E) = O(26V + E),用于存储 DP 数组、邻接表和队列