Hard
题目描述
给定一个有向无环图,包含 n 个节点,编号从 0 到 n - 1。图由长度为 m 的二维数组 edges 表示,其中 edges[i] = [ui, vi, costi] 表示从节点 ui 到节点 vi 的单向通信,恢复成本为 costi。
一些节点可能处于离线状态。给定一个布尔数组 online,其中 online[i] = true 表示节点 i 在线。节点 0 和 n - 1 始终在线。
从 0 到 n - 1 的路径是有效的,当且仅当:
- 路径上的所有中间节点都在线
- 路径上所有边的总恢复成本不超过 k
对于每条有效路径,定义其分数为该路径上最小边成本。
返回所有有效路径中的最大路径分数(即最大的最小边成本)。如果不存在有效路径,返回 -1。
示例 1:
输入:edges = [[0,1,5],[1,3,10],[0,2,3],[2,3,4]], online = [true,true,true,true], k = 10
输出:3
解释:
从节点 0 到节点 3 有两条可能的路径:
- 路径 0 → 1 → 3:总成本 = 5 + 10 = 15,超过 k,无效
- 路径 0 → 2 → 3:总成本 = 3 + 4 = 7 ≤ k,有效,最小边成本 = min(3, 4) = 3
示例 2:
输入:edges = [[0,1,7],[1,4,5],[0,2,6],[2,3,6],[3,4,2],[2,4,6]], online = [true,true,true,false,true], k = 12
输出:6
解释:
节点 3 离线,经过节点 3 的路径无效。
有效路径:
- 路径 0 → 1 → 4:总成本 = 12,最小边成本 = 5
- 路径 0 → 2 → 4:总成本 = 12,最小边成本 = 6
最大路径分数为 6
约束条件:
- n == online.length
- 2 ≤ n ≤ 5 × 10^4
- 0 ≤ m ≤ min(10^5, n × (n - 1) / 2)
- 0 ≤ ui, vi < n
- ui ≠ vi
- 0 ≤ costi ≤ 10^9
- 0 ≤ k ≤ 5 × 10^13
- online[0] 和 online[n-1] 都为 true
- 给定图是有向无环图
解题思路
这是一道结合二分搜索和最短路径算法的题目。核心思路如下:
问题转化: 我们要找最大的路径分数,其中路径分数定义为路径上最小边权。这提示我们可以用二分搜索答案。
二分搜索策略:
- 二分搜索可能的答案范围(最小边权的最大值)
- 对于每个候选值 mid,检查是否存在有效路径,其中所有边权都 ≥ mid,且总成本 ≤ k
检查函数实现: 由于图是 DAG,我们可以使用两种方法:
- Dijkstra 算法:在只包含边权 ≥ mid 的子图上运行
- 动态规划 + 拓扑排序:利用 DAG 性质,按拓扑顺序计算最短路径
算法流程:
- 收集所有边权作为二分搜索的候选答案
- 二分搜索最大可能的最小边权
- 对每个 mid 值,构建包含边权 ≥ mid 且连接在线节点的子图
- 在子图上运行最短路径算法,检查从 0 到 n-1 的最短路径是否 ≤ k
推荐使用 Dijkstra 算法,因为它更直观且容易实现。
代码实现
class Solution {
public:
int findMaxPathScore(vector<vector<int>>& edges, vector<bool>& online, long long k) {
int n = online.size();
// 收集所有边权作为候选答案
set<int> costs;
for (auto& edge : edges) {
costs.insert(edge[2]);
}
vector<int> candidates(costs.begin(), costs.end());
sort(candidates.rbegin(), candidates.rend()); // 降序排列
for (int cost : candidates) {
if (canReach(edges, online, k, cost, n)) {
return cost;
}
}
return -1;
}
private:
bool canReach(vector<vector<int>>& edges, vector<bool>& online, long long k, int minCost, int n) {
// 构建子图:只包含权重 >= minCost 且连接在线节点的边
vector<vector<pair<int, int>>> graph(n);
for (auto& edge : edges) {
int u = edge[0], v = edge[1], cost = edge[2];
if (cost >= minCost && online[u] && online[v]) {
graph[u].push_back({v, cost});
}
}
// Dijkstra 算法
vector<long long> dist(n, LLONG_MAX);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[0] = 0;
pq.push({0, 0});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) continue;
for (auto [v, cost] : graph[u]) {
long long newDist = dist[u] + cost;
if (newDist < dist[v]) {
dist[v] = newDist;
pq.push({newDist, v});
}
}
}
return dist[n-1] <= k;
}
};
class Solution:
def findMaxPathScore(self, edges: List[List[int]], online: List[bool], k: int) -> int:
import heapq
n = len(online)
# 收集所有边权作为候选答案
costs = sorted(set(edge[2] for edge in edges), reverse=True)
def can_reach(min_cost):
# 构建子图:只包含权重 >= min_cost 且连接在线节点的边
graph = [[] for _ in range(n)]
for u, v, cost in edges:
if cost >= min_cost and online[u] and online[v]:
graph[u].append((v, cost))
# Dijkstra 算法
dist = [float('inf')] * n
dist[0] = 0
pq = [(0, 0)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, cost in graph[u]:
new_dist = dist[u] + cost
if new_dist < dist[v]:
dist[v] = new_dist
heapq.heappush(pq, (new_dist, v))
return dist[n-1] <= k
for cost in costs:
if can_reach(cost):
return cost
return -1
public class Solution {
public int FindMaxPathScore(int[][] edges, bool[] online, long k) {
int n = online.Length;
// 收集所有边权作为候选答案
var costs = new HashSet<int>();
foreach (var edge in edges) {
costs.Add(edge[2]);
}
var candidates = costs.ToList();
candidates.Sort((a, b) => b.CompareTo(a)); // 降序排列
foreach (int cost in candidates) {
if (CanReach(edges, online, k, cost, n)) {
return cost;
}
}
return -1;
}
private bool CanReach(int[][] edges, bool[] online, long k, int minCost, int n) {
// 构建子图
var graph = new List<(int, int)>[n];
for (int i = 0; i < n; i++) {
graph[i] = new List<(int, int)>();
}
foreach (var edge in edges) {
int u = edge[0], v = edge[1], cost = edge[2];
if (cost >= minCost && online[u] && online[v]) {
graph[u].Add((v, cost));
}
}
// Dijkstra 算法
var dist = new long[n];
Array.Fill(dist, long.MaxValue);
var pq = new PriorityQueue<(long dist, int node), long>();
dist[0] = 0;
pq.Enqueue((0, 0), 0);
while (pq.Count > 0) {
var (d, u) = pq.Dequeue();
if (d > dist[u]) continue;
foreach (var (v, cost) in graph[u]) {
long newDist = dist[u] + cost;
if (newDist < dist[v]) {
dist[v] = newDist;
pq.Enqueue((newDist, v), newDist);
}
}
}
return dist[n-1] <= k;
}
}
var findMaxPathScore = function(edges, online, k) {
const n = online.length;
// 收集所有边权作为候选答案
const costs = [...new Set(edges.map(edge => edge[2]))];
costs.sort((a, b) => b - a); // 降序排列
function canReach(minCost) {
// 构建子图
const graph = Array(n).fill().map(() => []);
for (const [u, v, cost] of edges) {
if (cost >= minCost && online[u] && online[v]) {
graph[u].push([v, cost]);
}
}
// Dijkstra 算法
const dist = Array(n).fill(Infinity);
const pq = [[0, 0]]; // [distance, node]
dist[0] = 0;
while (pq.length > 0) {
pq.sort((a, b) => a[0] - b[0]);
const [d, u] = pq.shift();
if (d > dist[u]) continue;
for (const [v, cost] of graph[u]) {
const newDist = dist[u] + cost;
if (newDist < dist[v]) {
dist[v] = newDist;
pq.push([newDist, v]);
}
}
}
return dist[n-1] <= k;
}
for (const cost of costs) {
if (canReach(cost)) {
return cost;
}
}
return -1;
};
复杂度分析
| 复杂度类型 | 复杂度分析 |
|---|---|
| 时间复杂度 | O(E × (V log V + E)),其中 E 是边数,V 是节点数。需要对每个不同的边权运行一次 Dijkstra 算法 |
| 空间复杂度 | O(V + E),用于存储图结构和 Dijkstra 算法的辅助数组 |