云计算百科
云计算领域专业知识百科平台

2026-01-19:带传送的最小路径成本。用go语言,给定一个 m×n 的整数矩阵 grid 和一个整数 k。起点是左上角格子 (0,0),终点是右下角格子 (m−1,n−1)。你可以用两种方式前进

2026-01-19:带传送的最小路径成本。用go语言,给定一个 m×n 的整数矩阵 grid 和一个整数 k。起点是左上角格子 (0,0),终点是右下角格子 (m−1,n−1)。你可以用两种方式前进:

  • 相邻移动:向右或向下走一步(从 (i,j) 到 (i,j+1) 或 (i+1,j)),代价等于你进入的那个格子的数值;

  • 传送:从当前格子跳到任意一个数值不大于当前格子的格子(grid[x][y] ≤ grid[i][j]),传送不消耗代价,但最多只能使用 k 次。

目标是计算从起点到终点所需的最小总费用(所有移动代价之和)。

2 <= m, n <= 80。

m == grid.length。

n == grid[i].length。

0 <= grid[i][j] <= 10000。

0 <= k <= 10。

输入: grid = [[1,3,3],[2,5,4],[4,3,5]], k = 2。

输出: 7。

解释:

当前位置移动新位置总成本
(0, 0) 向下移动 (1, 0) 0 + 2 = 2
(1, 0) 向右移动 (1, 1) 2 + 5 = 7
(1, 1) 传送到 (2, 2) (2, 2) 7 + 0 = 7

我们最初在 (0, 0),成本为 0。

题目来自力扣3651。

🧠 算法过程详解

  • 初始化与边界检查
    算法首先获取网格的行数 m 和列数 n。它进行了一项关键检查:如果允许传送(k > 0)并且起点的值大于终点的值(grid[0][0] > grid[m-1][n-1]),则可以直接从起点传送到终点,总成本为0,算法直接返回0。这是因为传送规则允许跳到任意数值不大于当前格子的格子,且不消耗代价。

  • 确定数值范围并初始化关键数组
    算法遍历整个网格,找到其中最大的数值 mx,这定义了问题中格子数值的范围。
    接着,初始化三个关键数组:

    • sufMinF: 大小为 mx+2。sufMinF[x] 用于记录在当前迭代中,到达某个格子后,能够通过一次传送到达所有数值小于等于 x 的格子所需的最小累计成本。初始时,其每个元素被设置为一个极大值(math.MaxInt)。
    • minF: 大小为 mx+1。在当前行的计算过程中,minF[x] 临时记录到达当前行某个数值为 x 的格子的最小累计成本。每次计算新的一行前会被重置为极大值。
    • f: 大小为 n+1。这是一个动态规划数组,用于进行空间优化的路径计算。f[j] 代表在当前行,到达第 j 列格子的最小累计成本(注意索引偏移,f[1] 对应第0列)。
  • 核心迭代:模拟最多 k 次传送机会
    算法进行 k+1 次迭代(因为最多可以使用 k 次传送,包括0次)。每次迭代包含两个主要阶段:动态规划计算当前路径成本和更新传送优化信息。

    • 阶段一:动态规划计算路径成本
      在此阶段,算法忽略传送,计算从起点到每个格子的最小路径成本,规则是只能向右或向下移动,进入一个格子的成本是该格子的值。它使用滚动数组 f 进行空间优化:

      • 初始化 f 为极大值,并设置 f[1] = -grid[0][0]。这是因为起点(左上角)的成本不计入(根据题目描述,代价等于“进入的那个格子的数值”,起点是出发点,不应算作“进入”)。
      • 遍历每一行 i:
        • 遍历该行的每一列 j 和对应的格子值 x:
          • 计算 f[j+1] 的新值。它考虑两种移动方式:
          • 从左边格子向右移动过来:成本是 f[j] + x。
          • 从上边格子向下移动过来:成本是 f[j+1] + x(这里的 f[j+1] 是上一行同一列的值,在遍历过程中被保留)。
          • 关键点:此外,它还考虑第三种可能,即通过一次传送到达当前格子。这个信息来自 sufMinF[x],它代表了在之前的状态下,通过一次传送到达数值为 x 的格子的最小成本。
          • f[j+1] 取这三种情况的最小值。
        • 在计算完一行的 f 值后,更新 minF 数组:对于当前行的每个数值 x,minF[x] 更新为所有数值为 x 的格子的 f 值中的最小值。
    • 阶段二:更新传送优化信息
      这个阶段基于本轮动态规划计算出的 minF 来更新 sufMinF,为下一次迭代(即多使用一次传送)做准备。

      • sufMinF[i] 被更新为 min(sufMinF[i+1], minF[i])。这个操作是从大到小遍历数值,计算 sufMinF 的后缀最小值。其物理意义是:sufMinF[x] 现在表示,在本轮迭代后,可以通过一次传送到达数值 小于等于 x 的任意格子的最小成本。
      • 算法检查 sufMinF 数组是否在本轮迭代中发生了变化。如果没有变化(done 为 true),说明传送操作已经无法提供更优的路径,提前终止迭代。
  • 返回结果
    在完成所有迭代后,数组 f 的最后一个元素 f[n] 就包含了到达终点(右下角)的最小总成本,将其返回。

  • ⏱️ 复杂度分析

    • 时间复杂度:该算法的时间复杂度为 O(k * m * n * mx)。

      • 最外层循环是 k 次迭代。
      • 每次迭代中,需要遍历整个 m x n 的网格,复杂度为 O(m * n)。
      • 在每次迭代的最后,需要遍历 minF 数组(大小为 mx)来更新 sufMinF,复杂度为 O(mx)。
      • 因此,总复杂度是三者相乘。其中 mx 是网格中数值的最大值,在本题约束下(grid[i][j] <= 10000),mx 是一个常数上限(10000),但仍然是复杂度分析中的一个因子。
    • 空间复杂度:该算法的空间复杂度为 O(n + mx)。

      • 主要空间消耗在于 sufMinF 数组(O(mx))、minF 数组(O(mx))和动态规划数组 f(O(n))。
      • 算法使用了滚动数组技术,避免存储整个 m x n 的DP表,从而将空间复杂度降低到与网格维度无关的水平。

    💎 总结

    这个算法的核心思想是通过多次迭代(模拟使用传送次数)来逐步优化路径成本。在每次迭代中,它先计算不使用额外传送时的基础路径成本,然后利用上一轮迭代得到的传送信息(sufMinF)来更新当前路径成本的可能性。通过维护基于格子数值的代价后缀最小值数组,高效地捕捉了传送操作带来的优化效果。

    Go完整代码如下:

    package main

    import (
    "fmt"
    "math"
    "slices"
    )

    func minCost(grid [][]int, k int) int {
    m, n := len(grid), len(grid[0])
    if k > 0 && grid[0][0] > grid[m1][n1] {
    return 0
    }

    mx := 0
    for _, row := range grid {
    mx = max(mx, slices.Max(row))
    }

    sufMinF := make([]int, mx+2)
    for i := range sufMinF {
    sufMinF[i] = math.MaxInt
    }
    minF := make([]int, mx+1)
    f := make([]int, n+1)

    for range k + 1 {
    for i := range minF {
    minF[i] = math.MaxInt
    }

    // 64. 最小路径和(空间优化写法)
    for i := range f {
    f[i] = math.MaxInt / 2
    }
    f[1] = grid[0][0] // 起点的成本不算
    for _, row := range grid {
    for j, x := range row {
    f[j+1] = min(f[j]+x, f[j+1]+x, sufMinF[x])
    minF[x] = min(minF[x], f[j+1])
    }
    }

    done := true
    // 计算 minF 的后缀最小值
    for i := mx; i >= 0; i {
    mn := min(sufMinF[i+1], minF[i])
    if mn < sufMinF[i] {
    sufMinF[i] = mn
    done = false
    }
    }
    if done {
    // 收敛了:传送不改变 sufMinF,那么无论再传送多少次都不会改变 sufMinF
    break
    }
    }
    return f[n]
    }

    func main() {
    grid := [][]int{{1, 3, 3}, {2, 5, 4}, {4, 3, 5}}
    k := 2
    result := minCost(grid, k)
    fmt.Println(result)
    }

    在这里插入图片描述

    Python完整代码如下:

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

    import math
    from typing import List

    def minCost(grid: List[List[int]], k: int) > int:
    m, n = len(grid), len(grid[0])
    if k > 0 and grid[0][0] > grid[m1][n1]:
    return 0

    mx = max(max(row) for row in grid)

    sufMinF = [math.inf] * (mx + 2)
    minF = [math.inf] * (mx + 1)
    f = [math.inf] * (n + 1)

    for _ in range(k + 1):
    for i in range(len(minF)):
    minF[i] = math.inf

    # 64. 最小路径和(空间优化写法)
    for i in range(len(f)):
    f[i] = math.inf / 2
    f[1] = grid[0][0] # 起点的成本不算

    for row in grid:
    for j, x in enumerate(row):
    f[j+1] = min(f[j] + x, f[j+1] + x, sufMinF[x])
    if f[j+1] < minF[x]:
    minF[x] = f[j+1]

    done = True
    # 计算 minF 的后缀最小值
    for i in range(mx, 1, 1):
    mn = min(sufMinF[i+1], minF[i])
    if mn < sufMinF[i]:
    sufMinF[i] = mn
    done = False

    if done:
    # 收敛了:传送不改变 sufMinF,那么无论再传送多少次都不会改变 sufMinF
    break

    return int(f[n])

    if __name__ == "__main__":
    grid = [[1, 3, 3], [2, 5, 4], [4, 3, 5]]
    k = 2
    result = minCost(grid, k)
    print(result)

    在这里插入图片描述

    C++完整代码如下:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <climits>
    using namespace std;

    int minCost(vector<vector<int>>& grid, int k) {
    int m = grid.size(), n = grid[0].size();
    if (k > 0 && grid[0][0] > grid[m1][n1]) {
    return 0;
    }

    // 找到矩阵中的最大值
    int mx = 0;
    for (const auto& row : grid) {
    for (int val : row) {
    mx = max(mx, val);
    }
    }

    vector<int> sufMinF(mx + 2, INT_MAX);
    vector<int> minF(mx + 1, INT_MAX);
    vector<int> f(n + 1, INT_MAX / 2);

    for (int _ = 0; _ <= k; ++_) {
    // 重置 minF
    fill(minF.begin(), minF.end(), INT_MAX);

    // 64. 最小路径和(空间优化写法)
    fill(f.begin(), f.end(), INT_MAX / 2);
    f[1] = grid[0][0]; // 起点的成本不算

    for (const auto& row : grid) {
    for (int j = 0; j < n; ++j) {
    int x = row[j];
    // 计算三个可能值的最小值
    int val1 = f[j] + x;
    int val2 = f[j + 1] + x;
    int val3 = sufMinF[x];
    f[j + 1] = min({val1, val2, val3});

    if (f[j + 1] < minF[x]) {
    minF[x] = f[j + 1];
    }
    }
    }

    bool done = true;
    // 计算 minF 的后缀最小值
    for (int i = mx; i >= 0; i) {
    int mn = min(sufMinF[i + 1], minF[i]);
    if (mn < sufMinF[i]) {
    sufMinF[i] = mn;
    done = false;
    }
    }

    if (done) {
    // 收敛了:传送不改变 sufMinF,那么无论再传送多少次都不会改变 sufMinF
    break;
    }
    }

    return f[n];
    }

    int main() {
    vector<vector<int>> grid = {{1, 3, 3}, {2, 5, 4}, {4, 3, 5}};
    int k = 2;
    int result = minCost(grid, k);
    cout << result << endl;
    return 0;
    }

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 2026-01-19:带传送的最小路径成本。用go语言,给定一个 m×n 的整数矩阵 grid 和一个整数 k。起点是左上角格子 (0,0),终点是右下角格子 (m−1,n−1)。你可以用两种方式前进
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!