2026-03-29:网格中得分最大的路径。用go语言,给定一个大小为 m x n 的网格 grid,每个格子里的值只可能是 0、1、2,再给定一个整数 k。
从左上角 (0,0) 出发,要走到右下角 (m-1,n-1)。移动规则只有两种:向右或向下。每条路径由一串按顺序经过的格子组成。
路径过程中,每经过一个格子,会产生“分数”和“花费”的变化,规则如下:
-
格子值为 0:分数 +0,花费 +0
-
格子值为 1:分数 +1,花费 +1
-
格子值为 2:分数 +2,花费 +1
要求:所有经过的格子累加后的总花费不能超过 k。如果走不到终点,或者任意到达终点的路径总花费都超过 k,则认为不存在有效路径。
输出:在花费 ≤ k 的前提下,能得到的最大总分数;若不存在有效路径则返回 -1。
1 <= m, n <= 200。
0 <= k <= 1000。
grid[0][0] == 0。
0 <= grid[i][j] <= 2。
输入: grid = [[0, 1],[2, 0]], k = 1。
输出: 2。
解释:
最佳路径为:
| 单元格 | grid[i][j] | 当前分数 | 累计分数 | 当前花费 | 累计花费 |
|---|---|---|---|---|---|
| (0, 0) | 0 | 0 | 0 | 0 | 0 |
| (1, 0) | 2 | 2 | 2 | 1 | 1 |
| (1, 1) | 0 | 0 | 2 | 0 | 1 |
因此,可获得的最大分数为 2。
题目来自萝莉控3742。
一、先明确核心规则(必看)
- 移动限制:只能向右或向下走,从左上角(0,0)到右下角(1,1)
- 分数/花费规则:
- 格子=0:分数+0,花费+0
- 格子=1:分数+1,花费+1
- 格子=2:分数+2,花费+1
- 约束:总花费 ≤ 给定k,求最大分数;无有效路径返回-1
- 路径长度:从(0,0)到(1,1),固定走3个格子(2步),所有路径花费最多为2
二、第一步:计算「最小花费」(判断是否存在有效路径)
代码中第一个函数minPathSum的作用:计算从起点到终点的最小可能总花费。
因为只有当「最小花费 ≤ k」时,才存在有效路径;否则直接返回-1。
执行过程:
- 遍历网格每一行、每一个格子,按照规则累加最小花费
- 格子0不花钱,格子1/2都花1,计算所有路径中花钱最少的那条
- 样例计算结果:最小花费=1
- 对比k=1:1 ≤ 1 → 存在有效路径,继续计算最大分数
三、第二步:动态规划初始化(准备计算最大分数)
这一步是为了搭建计算框架,记录到达每个位置、花费不同步数时的最大分数。
核心概念:dp[列][总花费] = 最大分数
初始化细节:
- 网格大小:2行2列,k=1
- 优化:路径最大花费不超过
行数+列数-2(样例=2),所以k上限取1 - 创建二维记录数组:列数+1 × (k+2),所有值初始化为「负无穷」(代表不可达)
- 起点赋值:
(0,0)格子是0,分数0、花费0 → 初始状态:到达第1列、花费1时,分数为0
四、第三步:逐行逐格遍历计算(核心步骤)
按照从上到下、从左到右的顺序,遍历网格中每一个格子,更新每个位置的最大分数。 遍历顺序:(0,0) → (0,1) → (1,0) → (1,1)
子步骤1:处理格子 (0,0) (值=0)
- 分数+0,花费+0
- 无新计算,保持初始状态:分数0,花费0
子步骤2:处理格子 (0,1) (值=1)
- 只能从左边(0,0)向右走过来
- 规则:分数+1,花费+1
- 累计状态:分数=1,总花费=1
- 记录:到达该位置、花费1时,最大分数=1
子步骤3:处理格子 (1,0) (值=2)
- 只能从上面(0,0)向下走过来
- 规则:分数+2,花费+1
- 累计状态:分数=2,总花费=1
- 记录:到达该位置、花费1时,最大分数=2
子步骤4:处理终点格子 (1,1) (值=0)
这是最终目的地,有两条路径可以到达:
- 路径1:从上方(0,1)下来
- 原分数1,原花费1;格子0不加分、不花钱
- 最终:分数1,花费1
- 路径2:从左侧(1,0)过来
- 原分数2,原花费1;格子0不加分、不花钱
- 最终:分数2,花费1
- 取最大值:终点最大分数=2
- 总花费=1 ≤ k=1,符合要求
五、第四步:提取最终结果
遍历终点位置所有「花费≤k」的分数值,找到最大值:2 直接输出结果2。
时间复杂度 & 额外空间复杂度 分析
1. 时间复杂度
定义:
- 网格行数:
m,列数:n - 最大允许花费:
K
计算过程:
- 最小花费计算:遍历所有格子 →
- 最大分数计算:双重遍历格子 + 每层遍历花费K →
总时间复杂度: (m和n最大200,K最大1000,总运算量在合理范围内)
2. 额外空间复杂度
额外空间:除了输入网格外,代码主动开辟的内存空间
- 最小花费计算:开辟1个一维数组 →
- 最大分数计算:开辟1个二维数组
(n+1) × (K+2)→
总额外空间复杂度:
总结
- 解题分4步:算最小花费→初始化→逐格动态规划→取最大值
- 样例最优路径:(0,0)→(1,0)→(1,1),总分2,总花费1
- 时间复杂度:
- 额外空间复杂度:
Go完整代码如下:
package main
import (
"fmt"
"math"
"slices"
)
// 64. 最小路径和
func minPathSum(grid [][]int) int {
n := len(grid[0])
f := make([]int, n+1)
for j := range f {
f[j] = math.MaxInt
}
f[1] = 0
for _, row := range grid {
for j, x := range row {
f[j+1] = min(f[j], f[j+1]) + min(x, 1) // 值大于 0 的单元格花费 1
}
}
return f[n]
}
func maxPathScore(grid [][]int, K int) int {
if minPathSum(grid) > K {
return -1
}
m, n := len(grid), len(grid[0])
K = min(K, m+n-2) // 至多花费 m+n-2
f := make([][]int, n+1)
for j := range f {
f[j] = make([]int, K+2)
for k := range f[j] {
f[j][k] = math.MinInt
}
}
f[1][1] = 0
for i, row := range grid {
for j, x := range row {
for k := min(K, i+j); k >= 0; k-- { // 从 (0,0) 到 (i,j) 至多花费 i+j
newK := k
if x > 0 {
newK--
}
f[j+1][k+1] = max(f[j+1][newK+1], f[j][newK+1]) + x
}
}
}
return slices.Max(f[n])
}
func main() {
grid := [][]int{{0, 1}, {2, 0}}
k := 1
result := maxPathScore(grid, k)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
import math
def minPathSum(grid):
"""
64. 最小路径和
计算从左上角到右下角的最小路径和(值大于0的单元格花费1)
"""
n = len(grid[0])
f = [math.inf] * (n + 1)
f[1] = 0
for row in grid:
for j, x in enumerate(row):
f[j + 1] = min(f[j], f[j + 1]) + min(x, 1)
return int(f[n])
def maxPathScore(grid, K):
"""
计算最大路径分数
"""
if minPathSum(grid) > K:
return -1
m, n = len(grid), len(grid[0])
K = min(K, m + n - 2) # 至多花费 m+n-2
# 初始化DP表:f[j][k] 表示到达第j列时花费k的最大分数
f = [[-math.inf] * (K + 2) for _ in range(n + 1)]
f[1][1] = 0
for i, row in enumerate(grid):
for j, x in enumerate(row):
# 从 (0,0) 到 (i,j) 至多花费 i+j
max_k = min(K, i + j)
for k in range(max_k, -1, -1):
newK = k
if x > 0:
newK -= 1
if newK >= 0:
f[j + 1][k + 1] = max(
f[j + 1][newK + 1],
f[j][newK + 1]
) + x
else:
f[j + 1][k + 1] = -math.inf
return int(max(f[n]))
def main():
grid = [[0, 1], [2, 0]]
k = 1
result = maxPathScore(grid, k)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
// 64. 最小路径和
int minPathSum(vector<vector<int>>& grid) {
int n = grid[0].size();
vector<int> f(n + 1, INT_MAX);
f[1] = 0;
for (auto& row : grid) {
for (int j = 0; j < n; j++) {
int x = row[j];
f[j + 1] = min(f[j], f[j + 1]) + min(x, 1); // 值大于 0 的单元格花费 1
}
}
return f[n];
}
int maxPathScore(vector<vector<int>>& grid, int K) {
if (minPathSum(grid) > K) {
return -1;
}
int m = grid.size();
int n = grid[0].size();
K = min(K, m + n - 2); // 至多花费 m+n-2
// 初始化DP表:f[j][k] 表示到达第j列时花费k的最大分数
vector<vector<int>> f(n + 1, vector<int>(K + 2, INT_MIN));
f[1][1] = 0;
for (int i = 0; i < m; i++) {
auto& row = grid[i];
for (int j = 0; j < n; j++) {
int x = row[j];
// 从 (0,0) 到 (i,j) 至多花费 i+j
int max_k = min(K, i + j);
for (int k = max_k; k >= 0; k--) {
int newK = k;
if (x > 0) {
newK--;
}
if (newK >= 0) {
f[j + 1][k + 1] = max(f[j + 1][newK + 1], f[j][newK + 1]) + x;
} else {
f[j + 1][k + 1] = INT_MIN;
}
}
}
}
return *max_element(f[n].begin(), f[n].end());
}
int main() {
vector<vector<int>> grid = {{0, 1}, {2, 0}};
int k = 1;
int result = maxPathScore(grid, k);
cout << result << endl;
return 0;
}