经典动态规划题解

发布于:2025-09-13 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

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

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

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

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的情况,返回固定值,避免进入循环。

 
代码实现

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阶起步,均无额外成本)。

 
代码实现

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 存储中间值,并每次计算后取模。

 
代码实现

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则无法解码)。

 
代码实现

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、状态压缩)也能以此为基础扩展。


网站公告

今日签到

点亮在社区的每一天
去签到