经典动态规划题解

目录

[1. 1137. 第 N 个泰波那契数](#1. 1137. 第 N 个泰波那契数)

[2. 746. 使用最小花费爬楼梯](#2. 746. 使用最小花费爬楼梯)

[3. 746. 爬楼梯的变异:三步问题(waysToStep)](#3. 746. 爬楼梯的变异:三步问题(waysToStep))

[4. 91. 解码方法](#4. 91. 解码方法)

总结:动态规划的通用解题步骤


动态规划(DP)是算法中的重要思想,核心是"用已求结果推导当前结果",常能将暴力解法的高复杂度优化为线性。本文通过四道经典题目,拆解DP的基础思路与避坑要点。

1. 1137. 第 N 个泰波那契数

题目核心

求泰波那契数列的第n项,数列定义为: T(0)=0,T(1)=1,T(2)=1,T(n)=T(n-1)+T(n-2)+T(n-3) (n≥3)。

解题思路

**- 状态定义:无需额外DP数组,用三个变量 a、b、c 分别代表 T(n-3)、T(n-2)、T(n-1) ,通过迭代更新变量值,避免空间浪费。

  • 递推逻辑:从n=3开始,每次计算 T(n)=a+b+c ,再将 a→b 、 b→c 、 c→T(n) ,逐步推进到第n项。
  • 边界处理:直接判断n=0、1、2的情况,返回固定值,避免进入循环。**

代码实现

cpp 复制代码
class Solution {
public:
    int tribonacci(int n) {
        if(n==0)return 0;
        if(n==1||n==2)return 1;
        int a = 0, b = 1, c = 1;
        for(int i=3;i<=n;i++)
        {
            int d = a + b + c;
            a = b;
            b = c;
            c = d;
        }
        return c;
    }
};

**易错点

  • 无需用数组存储所有项:新手易习惯性定义 dpn 数组,但本题只需前三项结果,用变量迭代可将空间复杂度从O(n)降至O(1)。**

2. 746. 使用最小花费爬楼梯

题目核心

楼梯有n阶,每次可爬1或2阶,每阶对应"爬上去的成本" costi ,求到达楼顶(第n阶,无成本)的最小成本。

解题思路

**- 状态定义: dpi 表示到达第i阶的最小成本。

  • 递推逻辑:到达第i阶只有两种路径:
  1. 从第i-1阶爬1阶,成本为 dpi-1 + costi-1 ( costi-1 是第i-1阶的成本);
  2. 从第i-2阶爬2阶,成本为 dpi-2 + costi-2
    取两者最小值作为 dpi ,即 dpi = min(dpi-1\[icost\[i-1, dpi-2\[icost\[i-2) 。
  • 边界处理: dp0 = 0 (起点在第0阶,无需成本)、 dp1 = 0 (可直接从第0阶或第1阶起步,均无额外成本)。**

代码实现

cpp 复制代码
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n+1);
        for (int i = 2; i <= n; i++)
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        return dp[n];
    }
};

易错点

**- 混淆"阶数"与"cost数组下标":楼顶是第n阶,对应 dpn ,而 cost 数组仅到 costn-1 (第n-1阶的成本),需避免下标越界。

  • 初始状态错误:部分人会将 dp1 设为 cost0 ,但题目允许从第1阶直接起步,无需先爬第0阶,因此 dp1 应为0。**

3. 746. 爬楼梯的变异:三步问题(waysToStep)

题目核心

求爬到n阶台阶的方法数,每次可爬1、2或3阶,结果需对 1e9+7 取模(防止溢出)。

解题思路

**- 状态定义: dpi 表示爬到第i阶的总方法数。

  • 递推逻辑:到达第i阶的路径来自前3阶(爬1阶到i、爬2阶到i、爬3阶到i),因此 dpi = dpi-1 + dpi-2 + dpi-3
  • 边界处理: dp1=1 、 dp2=2 、 dp3=4 (直接枚举所有可能路径)。
  • 溢出处理:n较大时(如n=1e6),结果会超出 int 范围,需用 long long 存储中间值,并每次计算后取模。**

代码实现

cpp 复制代码
class Solution {
public:
    int waysToStep(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
        if (n == 3) return 4;
        long long a = 1, b = 2, c = 4;
        long long d;
        for (int i = 4; i <= n; ++i) {
            d = (a + b + c) % 1000000007;
            a = b;
            b = c;
            c = d;
        }
        return c;
    }
};

**易错点

  • 未处理数值溢出: int 最大约2e9,当n≥30时,方法数会超过 int 范围,必须用 long long 存储中间变量,且每次计算后取模(而非最后取模,避免中间值溢出)。
  • 边界值记忆错误:易将 dp3 误记为3(忽略"1+1+1""1+2""2+1""3"四种路径),需准确枚举初始值。**

4. 91. 解码方法

题目核心

将字符串 s (仅含数字)解码为字母(A=1,B=2,...,Z=26),求总解码方法数。

解题思路

**- 状态定义: dpi 表示字符串前i个字符( s0..i-1 )的解码方法数。

  • 递推逻辑:分两种情况判断当前字符的解码方式:
  1. 单个字符解码:若 si-1 != '0' (0无法单独解码),则 dpi += dpi-1 (前i-1个字符的方法数可直接延续);
  2. 两个字符解码:若前两个字符组成的数字 t 满足 10≤t≤26 (如"12"可解为L,"27"不可解),则 dpi += dpi-2 (前i-2个字符的方法数延续)。
  • 边界处理:
  • dp0 = 1 (空字符串有1种解码方式,作为递推基础);
  • dp1 = (s0 != '0') ? 1 : 0 (单个字符若为0则无法解码)。**

代码实现

cpp 复制代码
class Solution {
public:
    int numDecodings(string s) {
        int n=s.size();
        vector<int>dp(n+1);
        dp[0]=1; // 空字符串的解码方法数
        dp[1]=s[0]!='0'; // 单个字符的解码情况
        if(n==1)return dp[1];

        for(int i=2;i<=n;i++)
        {
            // 单个字符解码
            if(s[i-1]!='0')dp[i[i=dp[i-1];
            // 两个字符解码
            int t=(s[i-2]-'0')*10+(s[i-1]-'0');
            if(t>=10&&t<=26)dp[i[i=dp[i-2];
        }
        return dp[n];
    }
};

易错点

**- 忽略"0"的特殊处理:

  • 单个"0"无法解码,若 si-1 == '0' ,则不能用单个字符解码;
  • 两个字符中,若前一个是"0"(如"06"),组成的数字是6,不满足 10≤t≤26 ,无法解码。
  • 边界 dp0 的意义:新手易忽略 dp0=1 ,但当i=2且两个字符可解码时(如"12"), dp2 = dp1 + dp0 , dp0 的1是"空字符串+两个字符"的解码方式基础。
  • 字符串下标与 dp 下标错位: dpi 对应前i个字符( s0 到 si-1 ),计算两个字符时需取 si-2 和 si-1 ,避免下标越界。**

总结:动态规划的通用解题步骤

1. 定义状态:明确 dpi 代表的具体含义(如"到第i阶的方法数""前i个字符的解码数");
2. 推导递推公式:找到"当前状态与前几个状态的关系"(如泰波那契是前3项和,解码是单/双字符两种情况);
3. 处理边界条件:确定初始值(如 dp0 、 dp1 ),避免递推起始错误;
4. 优化空间(可选):若递推仅依赖前k个状态(如泰波那契依赖前3个),可用变量代替数组,将空间复杂度从O(n)降至O(1);
5. 规避特殊情况:如数值溢出(取模、用 long long )、无效输入(如"0""27")。

掌握这四道题的思路,可轻松应对大部分基础DP问题,后续复杂DP(如二维DP、状态压缩)也能以此为基础扩展。

相关推荐
别动我齐刘海8 小时前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
zhangfeng11338 小时前
《从“人工适配“到“智能生成“:KernelSwift 跨国产芯片算子迁移全栈方案解读》 —— 强调范式跃迁和跨硬件属性,适合偏架构分析的写法
人工智能·算法·华为·ai编程·npu
0+1118 小时前
算法 --滑动窗口
c++·算法·leetcode
reikocao9 小时前
大场景下 Hessian 特征值放大与退化检测
算法
hetao17338379 小时前
2026-09-18 hetao1733837 的刷题记录
c++·算法
wuyk5559 小时前
21.计数排序:不用比较的“空间换时间”排序算法
python·算法·排序算法
Rabitebla10 小时前
【Linux系统编程】 指令(二):一条路径是怎么定位到文件的
linux·c++·笔记·学习·算法
库玛西10 小时前
数据结构与算法之美:树、森林与二叉树全解析
c语言·数据结构·笔记·考研·算法·学习方法
木井巳10 小时前
【记忆化搜索】最长递增子序列
java·算法·leetcode·深度优先·剪枝·推荐算法
东方护航数据恢复(深圳)10 小时前
本地小算力设备数据恢复经典案例实操全解_东方护航数据恢复深圳店
大数据·服务器·算法·ai·gpu算力