经典动态规划题解

目录

[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;
    }
};

**易错点

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

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

题目核心

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

解题思路

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

  • 递推逻辑:到达第i阶只有两种路径:
  1. 从第i-1阶爬1阶,成本为 dp[i-1] + cost[i-1] ( cost[i-1] 是第i-1阶的成本);
  2. 从第i-2阶爬2阶,成本为 dp[i-2] + cost[i-2] 。
    取两者最小值作为 dp[i] ,即 dp[i] = min(dp[i-1[icost[i-1], dp[i-2[icost[i-2]) 。
  • 边界处理: dp[0] = 0 (起点在第0阶,无需成本)、 dp[1] = 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阶,对应 dp[n] ,而 cost 数组仅到 cost[n-1] (第n-1阶的成本),需避免下标越界。

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

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

题目核心

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

解题思路

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

  • 递推逻辑:到达第i阶的路径来自前3阶(爬1阶到i、爬2阶到i、爬3阶到i),因此 dp[i] = dp[i-1] + dp[i-2] + dp[i-3] 。
  • 边界处理: dp[1]=1 、 dp[2]=2 、 dp[3]=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 存储中间变量,且每次计算后取模(而非最后取模,避免中间值溢出)。
  • 边界值记忆错误:易将 dp[3] 误记为3(忽略"1+1+1""1+2""2+1""3"四种路径),需准确枚举初始值。**

4. 91. 解码方法

题目核心

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

解题思路

**- 状态定义: dp[i] 表示字符串前i个字符( s[0..i-1] )的解码方法数。

  • 递推逻辑:分两种情况判断当前字符的解码方式:
  1. 单个字符解码:若 s[i-1] != '0' (0无法单独解码),则 dp[i] += dp[i-1] (前i-1个字符的方法数可直接延续);
  2. 两个字符解码:若前两个字符组成的数字 t 满足 10≤t≤26 (如"12"可解为L,"27"不可解),则 dp[i] += dp[i-2] (前i-2个字符的方法数延续)。
  • 边界处理:
  • dp[0] = 1 (空字符串有1种解码方式,作为递推基础);
  • dp[1] = (s[0] != '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"无法解码,若 s[i-1] == '0' ,则不能用单个字符解码;
  • 两个字符中,若前一个是"0"(如"06"),组成的数字是6,不满足 10≤t≤26 ,无法解码。
  • 边界 dp[0] 的意义:新手易忽略 dp[0]=1 ,但当i=2且两个字符可解码时(如"12"), dp[2] = dp[1] + dp[0] , dp[0] 的1是"空字符串+两个字符"的解码方式基础。
  • 字符串下标与 dp 下标错位: dp[i] 对应前i个字符( s[0] 到 s[i-1] ),计算两个字符时需取 s[i-2] 和 s[i-1] ,避免下标越界。**

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

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

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

相关推荐
Pluchon2 小时前
硅基计划3.0 Map类&Set类
java·开发语言·数据结构·算法·哈希算法·散列表
☼←安于亥时→❦3 小时前
PyTorch之张量创建与运算
人工智能·算法·机器学习
子豪-中国机器人3 小时前
枚举算法和排序算法能力测试
开发语言·c++·算法
qiuyunoqy3 小时前
基础算法之二分算法 --- 2
算法
1白天的黑夜13 小时前
栈-844.比较含退格的字符串-力扣(LeetCode)
c++·leetcode·
爱干饭的boy3 小时前
手写Spring底层机制的实现【初始化IOC容器+依赖注入+BeanPostProcesson机制+AOP】
java·数据结构·后端·算法·spring
二哈不在线4 小时前
代码随想录二刷之“动态规划”~GO
算法·golang·动态规划
cellurw4 小时前
俄罗斯方块终端游戏实现 —— C语言系统编程与终端控制
c语言·算法
诸葛务农5 小时前
光电对抗:多模/复合制导中算法和软件平台
算法