一、线性动态规划的定义
具有线性阶段划分的动态规划算法称为线性动态规划(简称线性DP)。若状态包含多个维度,则每个维度都是线性划分的阶段,也属于线性DP。
1. 核心概念解读
-
动态规划(DP):是一种解决复杂问题的高效算法思想,其核心是将原问题分解为相对简单的子问题,并通过保存子问题的解(即“状态”)来避免重复计算,最终获得原问题的解。
-
“线性”的含义:这里的“线性”特指问题求解过程在阶段划分上是线性的、顺序的。就像一条线,从起点(初始状态)开始,按照明确的、不可跳跃的顺序(阶段1 -> 阶段2 -> ... -> 阶段n)一步步推进到终点(最终状态)。整个决策过程形成一个清晰的“链条”。

2. 示意图解构
-
状态:图中
状态0,状态1, ...,状态n,代表了在解决问题过程中,各个“时间点”或“步骤点”的情况记录。状态0是初始状态,状态n是目标状态。 -
决策:图中 每一个阶段都有每一个阶段的决策,
决策1,决策2, ...,决策n,代表从前一个状态向后一个状态转移时,我们所做出的选择。每个决策都会产生一定的“代价”或“收益”,并导致状态发生变化。 -
阶段:图中底部的
阶段1,阶段2, ...,阶段n, 正是线性划分的体现。每个阶段对应一次“决策+状态转移”的过程。阶段与阶段之间首尾相接,顺序固定,构成了线性的求解流程。 -
流程总结:整个算法从
状态0(初始条件)出发,在阶段1根据状态0做出决策1,从而更新到状态1;然后在阶段2, 再基于状态1做出决策2, 更新到状态2, 如此线性推进,直至在阶段n做出决策n后,到达最终的状态n,问题得以解决。
3. 对“多维度状态”的说明
定义的第二句话是理解的难点和关键。它指出,即使状态包含多个维度(例如用坐标 (i, j)表示位置),只要每个维度的变化是按线性顺序进行遍历或递推的,就仍然属于线性DP。
-
举例:在经典的“数字三角形”问题中,状态是二维的
(i, j),表示第i行第j列。我们通常的求解顺序是:从第一行开始,一行一行(线性遍历i)地向下计算,在每一行内,可能从左到右或按特定顺序(线性遍历j)计算每个位置的最优值。虽然状态是二维的,但两个维度(i和j)的变化过程本身都是线性、有序的阶段划分,因此它依然是线性DP。
总结
线性动态规划是一种将问题建模为“多阶段决策过程”的算法模型,其核心特征是阶段的线性、顺序依赖性。 无论状态是单一变量还是多维变量,只要决策推进的“时间线”或“逻辑顺序”是线性的,就可以用线性DP的思路来解决。图片中的示意图正是这一单向、链式决策过程的经典可视化表示。常见的最长上升子序列、背包问题、最短路径问题等,都是线性DP的典型应用。
实例讲解1: 爬楼梯
题目描述(HDU2041):一个楼梯共有M级台阶,刚开始时我们站在第1级台阶上,若每次只可以走上一级或二级台阶,则要走上第M级台阶共有多少种走法?
输入:第1行包含一个整数N,表示测试用例的个数。然后是N行数据,每行都包含一个整数M(1≤M≤40),表示楼梯的级数。
输出:对每个测试实例都输出不同走法的数量。
输入样例 输出样例
2 1
2 2
3
二、题意讲解
1. 问题理解
这是一个经典的动态规划问题,类似于斐波那契数列问题。我们需要计算从第1级台阶走到第M级台阶的不同走法数量,约束条件是:
-
每次只能向上走1级或2级台阶
-
起始位置是第1级台阶
-
目标位置是第M级台阶
2. 关键点分析
-
当M=1时:已经在第1级,不需要走,有0种走法(不动)
-
当M=2时:从第1级到第2级,只能走1步(1级),有1种走法
-
当M=3时:从第1级到第3级,有两种方式:
-
先走1级到第2级,再走1级到第3级
-
直接走2级到第3级
因此有2种走法
-
-
M=4:1+1+1、1+2、2+1,共3种。
三、解题思路
1. 数学递推关系
设f(n)表示从第1级走到第n级的走法数,则有以下递推关系:
-
f(1) = 0
-
f(2) = 1
-
f(3) = 2
-
f(n) = f(n-1) + f(n-2),当n ≥ 3时
解释:要到达第n级台阶,可以从第n-1级走1步上来,或者从第n-2级走2步上来。
只需要把n-1和n-2看成子问题,如果n-1对应a种走法,n-2对应b种走法,那么n级台阶就有a+b种走法。
1.原问题的最优解包含子问题的最优解,满足最优子结构
2.满足无后效性,因为从下往上走,和后边的子问题没有关系。
满足无后效性和最优子结构就可以使用动态规划。
2.解题方法
解法1:递归
/climbStairs_00_rec_00.cc
// 方法:递归
#include <iostream>
#include <vector>
using namespace std;
int fib(int n)
{
if (n <= 3) {
return n - 1;
}
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n = 0; // 测试数据组数
int m = 0; // 楼梯级数(1≤M≤40)
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> m;
cout << fib(m) << endl;
}
return 0;
}
复杂度分析:
-
时间复杂度:O(2ⁿ),因为递归树呈指数级增长。
-
空间复杂度:O(n),递归调用栈深度。
优点:
-
代码极简,直观反映数学定义。
缺点:
-
效率极低,对于 M=40 就会产生大量重复计算,不可用。
解法2:记忆化递归(自顶向下DP)
/climbStairs_00_rec_01.cc
思路:
在递归的基础上,使用一个数组 dp[] 保存已经计算过的 f(n) 值。当再次需要时直接返回,避免重复递归。
// 方法:记忆化递归
// 不需要重复求解已经计算过的数据
#include <iostream>
#include <vector>
using namespace std;
int recursion(int n, vector<int> & dp)
{
if (n <= 3 || dp[n] != 0) {
// != 0说明第n级楼梯的走法已经计算过,不需要重复计算,直接返回结果
return dp[n];

1472

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



