算法第三章作业

实践报告
作业“算法第三章3(2026)”题目“7-4 最低通行费”

一、题目简单说明

题目给出一个 N×N 的方格,每个格子里有一个通行费用。要求从左上角格子(0,0)走到右下角格子(N-1,N-1),每次只能向右走或者向下走,求一路上经过格子的最小费用总和。路径费用包括起点和终点格子的费用。

二、动态规划法求解步骤分析

1.1 根据最优子结构性质,列出递归方程式,说明方程式的定义、边界条件

设 dp[i][j] 表示从左上角起点(0,0)走到格子(i,j)时,所经过路径的最小通行费总和。因为每经过一个格子都要把该格子的费用算进去,所以 dp[i][j] 中包含当前格子(i,j)的费用 a[i][j]。

由于每次只能向右或向下走,所以走到格子(i,j)的最后一步只有两种可能:
第一种是从上面的格子(i-1,j)走下来;
第二种是从左边的格子(i,j-1)走过来。

因此,要想让走到(i,j)的总费用最小,就应该从上边和左边这两个方向中,选择一个费用更小的,再加上当前格子的费用。所以递归方程式为:

dp[i][j] = a[i][j] + min(dp[i-1][j], dp[i][j-1])

其中,如果 i=0,说明没有上面的格子;如果 j=0,说明没有左边的格子。这种情况下只考虑存在的那个方向。

边界条件如下:

起点:
dp[0][0] = a[0][0]

第一行只能一直向右走:
dp[0][j] = dp[0][j-1] + a[0][j],其中 j>0

第一列只能一直向下走:
dp[i][0] = dp[i-1][0] + a[i][0],其中 i>0

代码中把 best 先设成一个很大的数 1e9,然后如果上面有格子,就用 dp[i-1][j] 更新 best;如果左边有格子,就用 dp[i][j-1] 更新 best;最后 dp[i][j] = best + a[i][j]。这样写可以把第一行、第一列和普通格子的情况统一处理。

最优子结构可以这样理解:如果走到(i,j)的最优路线最后一步是从上面(i-1,j)来的,那么从起点走到(i-1,j)的那一段也一定是最优路线。否则,如果存在一条更便宜的路线走到(i-1,j),把它替换进去,就会得到一条走到(i,j)更便宜的路线,这和原来是最优路线矛盾。左边的情况也是一样的道理。所以原问题的最优解可以由子问题的最优解推出来。

1.2 给出填表法中表的维度、填表范围和填表顺序,以及原问题的最优值是哪个表格元素

填表法使用的表是二维数组 dp。

表的维度:
dp 是一个 N×N 的二维表,一共有 N 行 N 列。其中 dp[i][j] 表示从起点(0,0)走到格子(i,j)的最小通行费。

填表范围:
填表范围是所有格子,即:
0 ≤ i < N,0 ≤ j < N
也就是从第0行到第N-1行,从第0列到第N-1列,所有状态都要计算。

填表顺序:
程序中外层循环 i 从0到N-1,内层循环 j 从0到N-1,也就是从上到下、从左到右填表。这样安排的原因是:计算 dp[i][j] 时,需要用到上面的 dp[i-1][j] 和左边的 dp[i][j-1],按这个顺序填表可以保证这两个值已经算出来了。

原问题的最优值:
题目要求从左上角(0,0)走到右下角(N-1,N-1)的最低通行费,所以原问题的最优值就是:
dp[N-1][N-1]
程序最后输出 dp[N - 1][N - 1],和上面的分析是一致的。

1.3 分析该算法的时间和空间复杂度

时间复杂度:
dp 表一共有 N×N 个格子,也就是 N² 个状态。对于每一个状态 dp[i][j],只需要看上面和左边两个状态,做一次比较、一次加法和一次取最小值,这些都是常数时间的操作。因此总时间复杂度为:
O(N²)

空间复杂度:
程序中用了一个二维数组 a 来存输入的 N×N 费用矩阵,用了一个二维数组 dp 来存动态规划状态。两个数组的大小都是 N×N,所以总空间复杂度为:
O(N²)
如果只看动态规划辅助数组 dp,空间复杂度也是 O(N²)。进一步优化时,因为 dp[i][j] 只和上一行以及当前行左边的状态有关,所以可以用一维数组滚动更新,把动态规划辅助空间降到 O(N)。

三、对动态规划算法的理解和体会
动态规划就是把一个大问题拆成很多小问题,先把小问题的答案算出来并保存好,再用这些小问题的答案去推出大问题的答案。它适合用在子问题会重复出现、并且大问题的最优解可以由小问题的最优解推出来的题目中。
在“最低通行费”这个题目里,走到某个格子的最低费用,只和走到它上面格子的最低费用、走到它左边格子的最低费用有关。也就是说,当前格子的问题可以由上面和左边的小问题推出来,这就是最优子结构。不同路线可能会走到同一个中间格子,如果直接用递归,很多格子会被重复计算;而用填表法,每个格子只算一次,把结果存在 dp 表里,后面直接拿来用,效率就高很多。
通过这个题目,我觉得写动态规划程序时,最重要的有以下几点:第一,要先把状态定义清楚。比如本题中 dp[i][j] 定义为从起点走到格子(i,j)的最小费用。状态定义清楚了,后面的转移方程才好写。第二,要写出状态转移方程。本题中因为只能向右或向下走,所以当前格子只能从上面或左边过来,于是取上面和左边中较小的那个,再加上当前格子的费用。第三,要处理好边界条件。起点要单独初始化;第一行只能从左往右走;第一列只能从上往下走。边界处理不好,程序很容易出错。第四,要确定正确的填表顺序。因为 dp[i][j] 要用到上面和左边的值,所以按从上到下、从左到右的顺序填表,才能保证用到的值已经算好。第五,要清楚最后答案在表里的哪个位置。本题要求走到右下角,所以答案就是 dp[N-1][N-1]。
总的来说,动态规划不是简单地写递归,而是要先想清楚“状态是什么”“状态之间怎么转移”“边界是什么”“按什么顺序计算”“最后答案是哪个状态”。这个题目是一个比较典型的二维动态规划题,通过它我更好地理解了动态规划的基本步骤,也体会到保存中间结果、按顺序计算的重要性。

posted @ 2026-11-02 22:01  罗嘉惠  阅读(5)  评论(0)    收藏  举报