2026-10-02:有限电量到达目标节点的最少时间。用go语言,给定一张包含 n 个顶点的带权有向图,顶点编号为 0 到 n-1。图中的边由 edges

🤖 AI总结

主题

在带权有向图中,求解从起点到终点在电量约束下的最小时间路径,并最大化剩余电量。

摘要

该问题要求在带权有向图中,考虑节点离开时的电量消耗,找到从起点到终点总时间最小的路径,若有多条则选择剩余电量最大的。采用按剩余电量分层的动态规划方法,从高电量向低电量递推,每层用刷表法松弛后继状态,最终得到最小时间和对应最大剩余电量。

关键信息

  • 1 使用二维DP状态f[rem][u]表示剩余电量为rem时到达节点u的最小时间。
  • 2 按剩余电量从高到低遍历,保证状态转移的正确性。
  • 3 时间复杂度O(power*(n+m)),空间复杂度O(power*n+m)。

2026-10-02:有限电量到达目标节点的最少时间。用go语言,给定一张包含 n 个顶点的带权有向图,顶点编号为 0 到 n-1。图中的边由 edges 表示,每条边 [u, v, t] 表示从顶点 u 指向顶点 v,经过这条边需要耗时 t 秒。另有一个初始电量 power、一个长度为 n 的数组 cost,以及起点 source 和终点 target。cost[u] 表示信号要从顶点 u 沿任意一条出边继续发送时,需要消耗的电量。

信号在第 0 秒从 source 出发,初始电量为 power。到达某个顶点时不会扣电;只有当信号准备离开该顶点、沿某条出边继续前进时,才需要先保证当前剩余电量不少于 cost[u],然后剩余电量减少 cost[u]。每经过一条边,累计时间增加这条边的耗时。

现在要求计算信号从 source 到 target 的可行路径。优先使到达 target 的总时间最小;如果存在多条路径都能达到这个最小总时间,则选择到达 target 时剩余电量最大的那条路径。返回一个包含两个整数的结果:第一个是最小总时间,第二个是该最小总时间下的最大剩余电量。如果信号无法到达 target,则返回 [-1, -1]。

1 <= n <= 1000。

0 <= edges.length <= 1000。

edges[i] = [ui, vi, ti]。

0 <= ui, vi <= n – 1。

1 <= ti <= 100000。

1 <= power <= 1000。

cost.length == n。

1 <= cost[i] <= 2000。

0 <= source, target <= n – 1。

输入: n = 5, edges = [[0,1,1],[1,4,1],[0,2,1],[2,3,1],[3,4,1]], power = 4, cost = [2,3,1,1,1], source = 0, target = 4。

输出: [3,0]。

解释:

信号从节点 0 出发,拥有 4 个单位的电量。

路径 0 -> 1 -> 4 无效,因为离开节点 0 后,信号剩余 2 个单位的电量,这小于 cost[1] = 3。

有效路径 0 -> 2 -> 3 -> 4 总共花费时间为 3。

沿着这条路径消耗的总电量为 cost[0] + cost[2] + cost[3] = 4,剩余电量为 0。

因此,答案为 [3, 0]。

题目来自力扣3977。

1. 把图整理成邻接表

首先,代码把输入的边数组edges转换成邻接表。

每一条边[u, v, t]表示:

• 从节点u出发;

  • • 可以到达节点v;

  • • 经过这条边需要花费t秒。

    于是,对于每个节点u,都会保存一个列表,里面记录所有从u出发的边,以及每条边到达的目标节点和耗时。

    这样做的目的,是后面从某个节点扩展路径时,可以快速找到它的所有出边。

    2. 定义动态规划状态

    代码使用一个二维数组来记录状态,含义是:

    f[rem][u] 表示:从起点 source 出发,到达节点 u 时,如果剩余电量恰好是 rem,那么累计花费的最小时间是多少。

    也就是说,第一维表示剩余电量,第二维表示当前所在节点,值表示最小总时间。

    初始化时:

    • 在起点source,一开始剩余电量是power,时间为0;

  • • 所以f[power][source] = 0;

  • • 其他所有状态都初始化为一个很大的数,表示暂时不可达。

    这个状态定义很关键,因为它同时记录了“剩余电量”和“到达节点”,可以区分同样到达某个节点但剩余电量不同的情况。

    3. 从高电量向低电量遍历

    接下来,代码从剩余电量power开始,一直递减到0,逐层处理。

    为什么从高到低?

    因为信号每离开一个节点,都会消耗该节点的cost[u],而cost[u]是正数。所以:

    • 从高剩余电量状态出发,只会转移到更低剩余电量的状态;

  • • 不会从低电量转移到高电量。

    因此,按照剩余电量从高到低遍历,可以保证:

    • 当处理某个剩余电量rem时,所有能到达该状态的前驱状态都已经被处理过了;

  • • 用当前状态去更新更低剩余电量的状态,是安全的,不会遗漏或重复。

    这本质上是一种按剩余电量分层的动态规划。

    4. 每一层剩余电量的处理过程

    对于每一个剩余电量rem,代码做两件事:

    第一件事:检查是否能到达目标节点

    如果f[rem][target]不是无穷大,说明在剩余电量为rem的情况下,可以到达终点target,并且记录了一个总时间。

    此时代码会比较这个时间与当前已经记录的全局最小时间minDis:

    • 如果f[rem][target] < minDis,说明找到了更短的到达时间;

  • • 于是更新minDis为这个更短时间;

  • • 同时把maxRem更新为当前的剩余电量rem。

    由于外层循环是从power递减到0,所以:

    • 剩余电量大的状态会先被检查;

  • • 如果后面出现相同的最小时间,但剩余电量更小,因为判断条件是严格小于,所以不会覆盖;

  • • 因此最终保留的maxRem是所有达到最小时间的路径中,剩余电量最大的那个。

    这正好满足题目要求:先最小时间,再最大剩余电量。

    第二件事:从当前状态向外扩展

    接着,代码遍历所有节点x,查看f[rem][x]是否可达。

    如果f[rem][x]是无穷大,说明在剩余电量为rem时无法到达节点x,直接跳过。

    如果可达,还要判断当前剩余电量rem是否足够支付离开节点x所需的电量,也就是是否满足:

    rem >= cost[x]

    只有满足这个条件,信号才能从节点x沿任意一条出边继续前进。

    如果满足,则计算离开后的剩余电量:

    nxtRem = rem – cost[x]

    然后遍历节点x的所有出边。对于每一条出边x -> to,耗时为t:

    • 当前到达x的最小时间是f[rem][x];

  • • 经过这条边后,到达to的时间是f[rem][x] + t;

  • • 新的剩余电量是nxtRem;

  • • 如果这个时间比f[nxtRem][to]原来记录的时间更小,就更新它。

    这一步就是所谓的“刷表法”:用当前已经确定的状态,去松弛它能到达的后继状态。

    注意:到达某个节点本身不消耗电量,只有离开该节点时才消耗。因此这里是在“准备离开节点x”时扣除cost[x]。

    5. 为什么到达 target 后不需要再检查 cost

    代码在检查f[rem][target]时,只关心是否能到达终点,并不要求rem >= cost[target]。

    这是合理的,因为:

    • 信号到达target后已经完成任务;

  • • 不需要再从target离开;

  • • 所以不需要支付cost[target]。

    因此,只要某个剩余电量下能到达target,就是一个合法结果。

    6. 最终结果判断

    当所有剩余电量都处理完后:

    • 如果maxRem仍然是-1,说明从来没有到达过target,返回[-1, -1];

  • • 否则返回[minDis, maxRem],其中:

  • •minDis是最小总时间;

  • •maxRem是在这个最小总时间下,到达终点时最大的剩余电量。

    对于题目给的例子:

    • 路径0 -> 2 -> 3 -> 4总时间为3;

  • • 消耗电量为cost[0] + cost[2] + cost[3] = 2 + 1 + 1 = 4;

  • • 初始电量为4,所以剩余电量为0;

  • • 因此输出[3, 0]。

    7. 正确性简要说明

    这个算法覆盖了所有可能的路径,因为:

    • 状态中包含了“当前节点”和“剩余电量”;

  • • 每次扩展都严格按照消耗电量的规则进行;

  • • 电量只会减少,所以按电量从高到低处理不会漏掉任何可达状态;

  • • 对于每个状态,只保留到达该状态的最小时间;

  • • 最终在全局最小时间的前提下,由于遍历顺序是从高电量到低电量,所以保留的是最大剩余电量。

    因此,算法能够得到题目要求的结果。

    8. 时间复杂度

    设:

    • 节点数为n;

  • • 边数为m;

  • • 初始电量为power。

    代码外层循环剩余电量,共power + 1次。

    每次外层循环中:

    • 遍历所有节点,检查状态,复杂度为O(n);

  • • 对于可达且满足电量条件的节点,遍历其所有出边,所有出边总和最多为m,复杂度为O(m)。

    所以每一层剩余电量的处理复杂度是O(n + m)。

    总时间复杂度为:

    O(power * (n + m))

    代入题目限制,power <= 1000,n <= 1000,m <= 1000,规模大约在百万级别,是可以接受的。

    9. 额外空间复杂度

    主要额外空间包括:

    • 动态规划状态表f,大小为(power + 1) * n,所以是O(power * n);

  • • 邻接表存储图,节点数为n,边数为m,所以是O(n + m)。

    因此总额外空间复杂度为:

    O(power * n + n + m)

    通常可以简写为:

    O(power * n + m)

    因为n已经被power * n覆盖。

    总结一下:

    这个算法用“剩余电量”作为动态规划的一维,用“当前节点”作为另一维,记录到达每个状态的最小时间。然后从高电量到低电量逐层扩展,优先保证总时间最小,再在相同总时间下保留最大剩余电量。时间复杂度为O(power * (n + m)),额外空间复杂度为O(power * n + m)。

    Go完整代码如下:

    package main

    import (
    "fmt"
    "math"
    )

    func minTimeMaxPower(n int, edges [][]int, power int, cost []int, source int, target int) []int64 {
    type edge struct{ to, t int }
    g := make([][]edge, n)
    for _, e := range edges {
    x, y, t := e[0], e[1], e[2]
    g[x] = append(g[x], edge{y, t})
    }

    f := make([][]int, power+1)
    for i := range f {
    f[i] = make([]int, n)
    for j := range f[i] {
    f[i][j] = math.MaxInt
    }
    }
    f[power][source] = 0

    minDis, maxRem := math.MaxInt, -1
    for rem := power; rem >= 0; rem-- {
    if f[rem][target] < minDis {
    minDis, maxRem = f[rem][target], rem
    }
    for x, v := range f[rem] {
    if v == math.MaxInt || rem < cost[x] {
    continue
    }
    nxtRem := rem - cost[x]
    for _, e := range g[x] {
    f[nxtRem][e.to] = min(f[nxtRem][e.to], v+e.t) // 刷表法
    }
    }
    }

    if maxRem < 0 {
    return []int64{-1, -1}
    }
    return []int64{int64(minDis), int64(maxRem)}
    }

    func main() {
    n := 5
    edges := [][]int{{0, 1, 1}, {1, 4, 1}, {0, 2, 1}, {2, 3, 1}, {3, 4, 1}}
    power := 4
    cost := []int{2, 3, 1, 1, 1}
    source := 0
    target := 4
    result := minTimeMaxPower(n, edges, power, cost, source, target)
    fmt.Println(result)
    }

    2026-10-02:有限电量到达目标节点的最少时间。用go语言,给定一张包含 n 个顶点的带权有向图,顶点编号为 0 到 n-1。图中的边由 edges

    Python完整代码如下:

    # -*-coding:utf-8-*-

    from typing import List

    def minTimeMaxPower(
    n: int,
    edges: List[List[int]],
    power: int,
    cost: List[int],
    source: int,
    target: int
    ) -> List[int]:
    g = [[] for _ in range(n)]
    for u, v, t in edges:
    g[u].append((v, t))

    INF = 10**30
    f = [[INF] * n for _ in range(power + 1)]
    f[power][source] = 0

    min_dis = INF
    max_rem = -1

    for rem in range(power, -1, -1):
    if f[rem][target] < min_dis:
    min_dis = f[rem][target]
    max_rem = rem

    for x, cur_time in enumerate(f[rem]):
    if cur_time == INF or rem < cost[x]:
    continue

    nxt_rem = rem - cost[x]
    for to, t in g[x]:
    new_time = cur_time + t
    if new_time < f[nxt_rem][to]:
    f[nxt_rem][to] = new_time

    if max_rem < 0:
    return [-1, -1]

    return [min_dis, max_rem]

    if __name__ == "__main__":
    n = 5
    edges = [[0, 1, 1], [1, 4, 1], [0, 2, 1], [2, 3, 1], [3, 4, 1]]
    power = 4
    cost = [2, 3, 1, 1, 1]
    source = 0
    target = 4

    result = minTimeMaxPower(n, edges, power, cost, source, target)
    print(result)

    2026-10-02:有限电量到达目标节点的最少时间。用go语言,给定一张包含 n 个顶点的带权有向图,顶点编号为 0 到 n-1。图中的边由 edges

    C++完整代码如下:

      
    
    using namespace std;


    vector minTimeMaxPower(
    int n,
    vector int >>& edges,
    int power,
    vector< int >& cost,
    int source,
    int target
    ) {
    struct Edge {
    int to, t;
    };

    vector > g(n);
    for (auto& e : edges) {
    int x = e[ 0 ], y = e[ 1 ], t = e[ 2 ];
    g[x].push_back({y, t});
    }

    const long long INF = LLONG_MAX / 4 ;

    // f[rem][u] 表示到达节点 u 且剩余电量为 rem 时的最小时间
    vector > f(power + 1 , vector (n, INF));
    f[power][source] = 0 ;

    long long minDis = INF;
    int maxRem = -1 ;

    for ( int rem = power; rem >= 0 ; --rem) {
    if (f[rem][target] < minDis) {
    minDis = f[rem][target];
    maxRem = rem;
    }

    for ( int x = 0 ; x < n; ++x) {
    long long curTime = f[rem][x];
    if (curTime == INF || rem < cost[x]) {
    continue ;
    }

    int nxtRem = rem - cost[x];
    for (auto& e : g[x]) {
    if (curTime + e.t < f[nxtRem][e.to]) {
    f[nxtRem][e.to] = curTime + e.t;
    }
    }
    }
    }

    if (maxRem < 0 ) {
    return { -1 , -1 };
    }

    return {minDis, (long long)maxRem};
    }

    int main() {
    int n = 5 ;
    vector int >> edges = {
    { 0 , 1 , 1 },
    { 1 , 4 , 1 },
    { 0 , 2 , 1 },
    { 2 , 3 , 1 },
    { 3 , 4 , 1 }
    };
    int power = 4 ;
    vector< int > cost = { 2 , 3 , 1 , 1 , 1 };
    int source = 0 ;
    int target = 4 ;

    vector result = minTimeMaxPower(n, edges, power, cost, source, target);
    cout << "[" << result[ 0 ] << ", " << result[ 1 ] << "]" << endl;

    return 0 ;
    }

    2026-10-02:有限电量到达目标节点的最少时间。用go语言,给定一张包含 n 个顶点的带权有向图,顶点编号为 0 到 n-1。图中的边由 edges

    我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

    © 版权声明

    相关文章