文章目录
一、算法概述
- 定义:二维线性动态规划(Dynamic Programming, DP)是指状态由两个变量描述,且状态转移关系呈现线性递推特征的动态规划问题。这类问题通常需要填充二维表格来保存子问题的解,最终通过表格中的值推导出原问题的解。
- 核心思想:将复杂问题分解为重叠子问题,通过存储子问题的解(状态)避免重复计算,利用状态间的递推关系(状态转移方程)逐步求解。
- 应用场景:广泛应用于路径规划、序列比对、资源分配、组合优化等问题,如背包问题、最长公共子序列、矩阵链乘法等。
二、算法思路
- 状态定义:设计合适的二维数组
dp[i][j],其中i和j表示问题的两个维度,dp[i][j]表示对应子问题的解。例如:- 在数字三角形问题中,
dp[i][j]表示从顶部到第i行第j列的最大路径和。 - 在网格路径计数问题中,
dp[i][j]表示从起点到第i行第j列的路径数目。
- 在数字三角形问题中,
- 状态转移方程:根据问题的最优子结构性质,推导出状态间的递推关系。常见的转移形式包括:
- 当前状态由上方和左方状态转移而来:
dp[i][j] = f(dp[i-1][j], dp[i][j-1])。 - 当前状态由上方、左方和左上方状态转移而来:
dp[i][j] = f(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])。
- 当前状态由上方和左方状态转移而来:
- 边界条件:初始化边界状态的值,通常是第一行和第一列的状态,或某些特殊位置的状态。
- 遍历顺序:根据状态转移方程的依赖关系,确定合适的遍历顺序,确保计算当前状态时所需的所有前驱状态已被计算。
三、伪代码实现
1. 数字三角形最大路径和
算法:数字三角形最大路径和
输入:数字三角形 mat[0..n-1][0..i](i为当前行索引)
输出:从顶部到底部的最大路径和
function maxPathSum(n, mat):
创建二维数组 dp[0..n-1][0..n-1]
// 初始化顶部元素
dp[0][0] = mat[0][0]
// 处理边界:每行的第一个和最后一个元素
for i from 1 to n-1:
dp[i][0] = mat[i][0] + dp[i-1][0] // 第一列只能从上一行下来
dp[i][i] = mat[i][i] + dp[i-1][i-1] // 对角线只能从左上过来
// 处理中间元素
for i from 1 to n-1:
for j from 1 to i-1:
dp[i][j] = mat[i][j] + max(dp[i-1][j-1], dp[i-1][j])
// 找到最后一行的最大值
maxSum = dp[n-1][0]
for i from 1 to n-1:
if dp[n-1][i] > maxSum:
maxSum = dp[n-1][i]
return maxSum
// 主程序
输入 n
输入数字三角形 mat
输出 maxPathSum(n, mat)
2. 网格路径计数(带障碍物)
算法:网格路径计数(带障碍物)
输入:n×n网格 s[0..n-1][0..n-1]('*'表示障碍物)
输出:从左上角到右下角的路径数目(结果对 mod 取余)
function pathCount(n, s):
常量 mod = 1000000007
创建二维数组 dp[0..n-1][0..n-1]
for i from 0 to n-1:
for j from 0 to n-1:
if s[i][j] == '*':
dp[i][j] = 0 // 障碍物处无法到达
else:
if i == 0 and j == 0:
dp[i][j] = 1 // 起点初始化为1
else if i == 0: // 第一行只能从左边来
dp[i][j] = dp[i][j-1]
else if j == 0: // 第一列只能从上边来
dp[i][j] = dp[i-1][j]
else: // 中间位置可以从上方或左方来
dp[i][j] = (dp[i-1][j] + dp[i][j-1]) % mod
return dp[n-1][n-1]
// 主程序
输入 n
输入网格 s
输出 pathCount(n, s)
四、算法解释
1. 数字三角形最大路径和
- 状态定义:
dp[i][j]表示从顶部到第i行第j列的最大路径和。 - 转移方程:对于非边界位置,
dp[i][j] = mat[i][j] + max(dp[i-1][j-1], dp[i-1][j]),即当前位置的最大路径和等于当前值加上上方或左上方的较大值。 - 边界处理:第一列和对角线元素只能从特定方向转移而来,需单独处理。
2. 网格路径计数
- 状态定义:
dp[i][j]表示从左上角到第i行第j列的路径数目。 - 转移方程:无障碍时,
dp[i][j] = dp[i-1][j] + dp[i][j-1],即路径数目为上方和左方路径数之和;有障碍物时为 0。 - 边界处理:起点初始化为 1,第一行和第一列的路径数由前一个位置决定。
五、复杂度分析
1. 时间复杂度
- 数字三角形:双重循环遍历每个元素,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 网格路径计数:双重循环遍历每个网格,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 激光器开关方案数:单重循环遍历每个激光器,时间复杂度为 O ( n ) O(n) O(n)(此处 n = 30 n=30 n=30 为固定值)。
2. 空间复杂度
- 数字三角形:使用二维数组存储状态,空间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 网格路径计数:使用二维数组存储状态,空间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 激光器开关方案数:使用二维数组存储状态,但仅需保存前一个状态,可优化至 O ( 1 ) O(1) O(1),此处为 O ( n ) O(n) O(n)(固定大小)。
3. 优化方向
- 对于某些问题,若状态转移仅依赖于前一行或前一列,可将二维数组优化为一维数组,空间复杂度降为 O ( n ) O(n) O(n)。
- 在路径计数问题中,若网格规模较大,可通过预处理或数学方法进一步优化。
本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。
755

被折叠的 条评论
为什么被折叠?



