《二维线性DP》基础概念

一、算法概述

  1. 定义:二维线性动态规划(Dynamic Programming, DP)是指状态由两个变量描述,且状态转移关系呈现线性递推特征的动态规划问题。这类问题通常需要填充二维表格来保存子问题的解,最终通过表格中的值推导出原问题的解。
  2. 核心思想:将复杂问题分解为重叠子问题,通过存储子问题的解(状态)避免重复计算,利用状态间的递推关系(状态转移方程)逐步求解。
  3. 应用场景:广泛应用于路径规划、序列比对、资源分配、组合优化等问题,如背包问题、最长公共子序列、矩阵链乘法等。

二、算法思路

  1. 状态定义:设计合适的二维数组 dp[i][j],其中 ij 表示问题的两个维度,dp[i][j] 表示对应子问题的解。例如:
    • 在数字三角形问题中,dp[i][j] 表示从顶部到第 i 行第 j 列的最大路径和。
    • 在网格路径计数问题中,dp[i][j] 表示从起点到第 i 行第 j 列的路径数目。
  2. 状态转移方程:根据问题的最优子结构性质,推导出状态间的递推关系。常见的转移形式包括:
    • 当前状态由上方和左方状态转移而来: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])
  3. 边界条件:初始化边界状态的值,通常是第一行和第一列的状态,或某些特殊位置的状态。
  4. 遍历顺序:根据状态转移方程的依赖关系,确定合适的遍历顺序,确保计算当前状态时所需的所有前驱状态已被计算。

三、伪代码实现

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入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值