一、题目

二、做题思路
2.1 状态表示(核心基础)
本题要求计算到达每一级台阶 所需的最小花费。我们定义 dp[i] 表示到达第 i 级台阶(即下标为 i 的位置)时的最小累计花费 。注意,楼梯顶部对应于下标为 n (n 为数组长度),即越过最后一个台阶。该定义以位置为结尾 进行统计,是后续推导的出发点。
2.2 状态转移方程(关键难点)
可以选择向上爬 1 个台阶或 2 个台阶 ,因此到达第 i 级台阶的最后一步 要么来自 i-1(支付 cost[i-1]),要么来自 i-2(支付 cost[i-2])。为了花费最小 ,我们在两者中取较小值:
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]) (其中 i ≥ 2)。
该方程将全局最优 拆解为子问题最优,是动态规划的核心。
2.3 初始化(边界防护)
题目允许从下标 0 或 1 的台阶开始 ,意味着到达这两个位置无需支付任何费用 ,因此:
dp[0] = 0 ,dp[1] = 0 。
同时,cost 数组中的费用仅用于离开该台阶时支付,与到达时的累计花费无关,初始化必须符合这一语义。
2.4 填表顺序(递推方向)
计算 dp[i] 必须依赖 dp[i-1] 和 dp[i-2],这些下标均小于 i。因此必须从左到右 (即下标从小到大)依次填充 dp 表,保证每个状态计算时,其所有前置状态均已就绪。
2.5 返回值(目标映射)
题目要求返回到达楼梯顶部的最小花费 ,而楼梯顶部对应下标 n (n = cost.length),dp[n] 正好表示该值。因此直接返回 dp[n]。
三、代码
cpp
#include <vector>
using namespace std;
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost)
{
int n = cost.size();
//1.创建dp表
vector<int> dp(n + 1);
//3. 初始化
dp[0] = dp[1] = 0;//可以从0和1开始爬因此不需要交钱
//2.状态转移方程
for (int i = 2; i <= n; i++)
{
//4.填写顺序从左往右
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);//求上一步和上上步谁花钱最少到这
}
return dp[n];
}
};
四、流程图

五、题目

六、做题思路
6.1 状态表示(核心基础)
本题要求计算给定字符串 s 的解码方法总数 。我们定义 dp[i] 表示字符串前 i 个字符(即 s[0..i-1])的解码方法数 。该定义以前缀长度 为状态,直接对应题目所求,是后续推导的出发点。
6.2 状态转移方程(关键难点)
解码第 i 个字符(位置 i-1)时,最后一步有两种可能:
-
单独解码
s[i-1],要求该字符不为'0',此时贡献dp[i-1]。 -
与前一字符组合 ,要求
s[i-2]不为'0'且两位数字在 10 ~ 26 之间,此时贡献dp[i-2]。
因此,方程可写为 dp[i] = dp[i-1] + dp[i-2] ,但两项仅在对应条件满足时才累加,具体为:
-
若
s[i-1] != '0',则dp[i-1]有效;否则此项为 0。 -
若
s[i-2] != '0'且(s[i-2]-'0')*10 + (s[i-1]-'0') <= 26,则dp[i-2]有效;否则此项为 0。
6.3 初始化(边界防护)
为递推提供起始条件,需设定:
-
dp[0] = 1,表示空串有 1 种解码方式(基础情况)。 -
dp[1]取决于第一个字符:若s[0] != '0',则dp[1] = 1,否则dp[1] = 0。若字符串以
'0'开头,初始化已直接导致最终结果为 0,符合题意。
6.4 填表顺序(递推方向)
计算 dp[i] 依赖 dp[i-1] 和 dp[i-2],下标均小于 i。因此必须从左到右 (即 i 从 2 到 n)依次填充 dp 表,确保每个状态计算时,其所有前置状态均已就绪。
6.5 返回值(目标映射)
题目要求返回整个字符串 s 的解码方法总数 ,即长度为 n 的前缀,对应 dp[n] 。因此直接返回 dp[n]。若过程中任何位置无法解码,转移方程会自动将对应项置零,最终返回 0,满足题目要求。
七、代码
cpp
class Solution {
public:
int numDecodings(string s)
{
int n = s.size();
vector<int> dp(n);//创建dp表
//初始化
if (s[0] != '0')
dp[0] = 1;
if (n == 1) return dp[0];//边界
if (s[0] != '0' && s[1] != '0') dp[1] += 1;
int t = (s[0] - '0') * 10 + s[1] - '0';
if (t >= 10 && t <= 26)
dp[1] += 1;
for (int i = 2; i < n; i++)
{
//状态转移方程dp[i]=dp[i-1]+dp[i-2]
if (s[i] != '0') dp[i] += dp[i - 1];
int t = (s[i - 1] - '0') * 10 + s[i] - '0';
if (t >= 10 && t <= 26)
dp[i] += dp[i - 2];
}
return dp[n - 1];
}
};
八、流程图

九、优化代码
cpp
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
// 1. 创建dp表
// dp[i] 表示字符串 s 的前 i 个字符(即 s[0..i-1])的解码方法总数
vector<int> dp(n + 1, 0);
// 2. 初始化
// dp[0] = 1:空串可以视为一种解码方式,作为后续累加的基础。
// 为什么给1?当处理两位数字时,若 s[1] 和 s[0] 构成合法两位数,
// 则 dp[2] 需要加上 dp[0],此时 dp[0]=1 代表"两位数字作为一个整体"这一种情况。
dp[0] = 1;
// 处理第一个字符(索引0),若不为 '0',则单独解码成一种方案
if (s[0] != '0')
dp[1] = 1;
// 如果字符串只有一个字符,直接返回结果(避免后续越界)
if (n == 1) return dp[1];
// 3. 填表顺序:从左到右(i 从 2 到 n)
for (int i = 2; i <= n; i++) {
// 情况一:当前字符 s[i-1] 单独解码(必须不为 '0')
if (s[i - 1] != '0')
dp[i] += dp[i - 1];
// 情况二:当前字符与前一个字符 s[i-2] 组成两位数
int twoDigit = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
// 合法范围:10 ~ 26(注意 '01' 等前导零不合法,所以 t >= 10 已排除)
if (twoDigit >= 10 && twoDigit <= 26)
dp[i] += dp[i - 2];
}
// 4. 返回值:dp[n] 即为整个字符串的解码方法数
return dp[n];
}
};