第三章 最低通行费

首先需要明确题目的一个核心隐藏条件:商人必须在 (2N-1) 个单位时间穿越出去。从左上角 (1,1) 到右下角 (N,N),如果只能向右或向下走,恰好需要走 (N-1) + (N-1) 步(移动次数),加上起点和终点,正好经历 2N-1 个方格。因此,这个时间限制实际上等价于:商人只能向右或向下移动,不能走回头路。
以下是具体的动态规划法求解步骤分析:
1.1 最优子结构性质与递归方程式
状态定义:
设 dp[i][j] 表示从网格左上角 (1,1) 走到网格中第 i 行第 j 列 (i,j) 位置时,所需花费的最低通行费。
2. 最优子结构性质:
到达网格中任意一个位置 (i,j),只有两种可能:从正上方的 (i-1,j) 走下来,或者从左方的 (i,j-1) 走过来。因此,原问题的最优解必然包含其子问题的最优解。即走到 (i,j) 的最低费用,是由走到 (i-1,j) 或 (i,j-1) 的最低费用加上 (i,j) 本身的费用构成的。
3. 递归方程式:
当 i > 1 且 j > 1 时:
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + cost[i][j]
(其中 cost[i][j] 表示进入该方格需要缴纳的费用)
4. 边界条件:
起点:dp[1][1] = cost[1][1] (一开始就处于左上角,必须缴纳该方格费用)。
第一行 (i=1, j>1):只能从左边走过来。
dp[1][j] = dp[1][j-1] + cost[1][j]
第一列 (j=1, i>1):只能从上边走过来。
dp[i][1] = dp[i-1][1] + cost[i][1]
1.2 填表法分析
表的维度:需要一个二维数组(二维表)dp,维度大小为 N × N (如果为了方便处理边界采用 1-based 索引,可以定义为 (N+1) × (N+1))。
填表范围:对于 1 <= i <= N 和 1 <= j <= N 的所有格子都要填。
填表顺序:从上到下,从左到右(即外层循环 i 从 1 到 N,内层循环 j 从 1 到 N)。
理由:根据递归方程式,计算 dp[i][j] 依赖于其上方 dp[i-1][j] 和左方 dp[i][j-1] 的值。按行优先遍历可以保证在计算当前格子时,其上方和左方的子问题都已经被计算完毕。
原问题的最优值:整个二维表右下角的元素,即 dp[N][N]。
时间复杂度:O(N^2)
算法使用了两层嵌套循环遍历 N × N 的网格,每次状态转移(比较上方和左方并相加)只需常数时间 O(1),因此总时间复杂度为 O(N^2)。对于 N < 100 的数据规模,完全可以在题目要求的时限内运行完毕。
空间复杂度:O(N^2)
需要一个大小为 N × N 的二维数组来存储所有的状态值。
(进阶优化:由于 dp[i][j] 只依赖于当前行的左边和上一行的正上方,我们可以使用滚动数组将空间复杂度优化至 O(N)。即只维护一个一维数组,每次更新时 dp[j] = min(dp[j], dp[j-1]) + cost[i][j] 。当然,对于本题 N<100 的数据规模,直接使用二维数组更易于理解和实现。)
对动态规划算法的理解和体会
动态规划本质上是一种“以空间换时间”的算法思想。通过分析这道题,我有了以下几点深刻的体会:
化繁为简,全局最优依赖于局部最优:DP 的核心在于寻找“最优子结构”。面对庞大复杂的问题,不要试图一步登天,而是去想“我现在的这一步是怎么从前面走过来的”。找准了状态转移方程,问题就迎刃而解。
“记忆化”是精髓:传统的暴力递归(如DFS)在这类问题中会产生大量的重叠子问题(同一个格子会被计算多次)。DP 填表法的本质就是建立一个备忘录,把算过的结果存起来,下次用到直接查表,极大地提升了效率。
边界条件极其关键:在写代码时,最容易出错的地方往往不是状态转移方程,而是边界(如第一行和第一列的处理)。必须明确“初始化状态”,这是递推的起点。

posted @ 2026-10-27 18:15  徐高强  阅读(6)  评论(0)    收藏  举报