文章目录
- 1.第N个泰波那契数(简单)
- 解题流程
- 1. 状态表示
- 2. 状态转移方程
- 3. 初始化dp表
- 4. 填表顺序
- 5. 返回值
- 代码编写
- 2.三步问题
- 解题流程
- 1. 状态表示
- 2. 状态转移方程
- 3. 初始化dp表
- 4. 填表顺序
- 5. 返回值
- 代码编写
- 3.使用最小花费爬楼梯
- 解题流程
- 1. 状态表示
- 2. 状态转移方程
- 3. 初始化dp表
- 4. 填表顺序
- 5. 返回值
- 代码编写
- 4.解码方法(中等)
- 解题流程
- 1. 状态表示
- 2. 状态转移方程
- 3. 初始化dp表
- 4. 填表顺序
- 5. 返回值
- 代码编写
1.第N个泰波那契数(简单)
-
题目链接:第N个泰波那契数
-
题目描述:
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
解题流程
1. 状态表示
在解任何一道动态规划题目时,我们都需要先给出一张dp
表,用来存储某种状态。
dp[i]:在本题目中,dp[i]表示的含义是第i个泰波纳契数的值
;
2. 状态转移方程
所谓状态转移方程就是,用已经存在的状态来推出将要发生的状态。
在本题中,状态转移方程为——
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
;
3. 初始化dp表
从状态转移方程中,我们发现当 i < 3 时,方程是失效的,因为会出现 dp 表的下标为负数的情况。所以,也就是说,当 i = 0,i = 1,i = 2时,不能依靠状态转移方程推出 dp[i] 的值,则需要我们手动进行初始化,即:
dp[0] = 0
,dp[1] = 1
,dp[2] = 1
;
4. 填表顺序
填表顺序即,填 dp 表的顺序。
- 本题中,我们发现要想推导出当前位置的值,首先要知道前面 3 个 dp 值,所以,填表顺序为
从左往右
。
5. 返回值
- dp[i] 的含义本来就是第 i 个泰波纳契数的值,所以返回值为
dp[n]
;
代码编写
C++
class Solution {
public:
int tribonacci(int n) {
// 特殊情况处理
if(n == 0) return 0;
if(n == 1 || n == 2) return 1;
// 创建dp表
vector<int> dp(n+1);
// 初始化
dp[0] = 0; dp[1] = 1; dp[2] = 1;
// 填表
for(int i = 3; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
// 返回
return dp[n];
}
};
JAVA
class Solution
{
public int tribonacci(int n)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回结果
// 处理边界情况
if(n == 0) return 0;
if(n == 1 || n == 2) return 1;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = dp[2] = 1;
for(int i = 3; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
return dp[n];
}
}
2.三步问题
-
题目链接:三步问题
-
题目描述:
三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
解题流程
1. 状态表示
dp[i]:到达第 i 个台阶有多少种方法
;
2. 状态转移方程
题目中这个小孩的一次最多能跨三个台阶,也就是说当我们要计算到达 i 位置时有多少种方式,可以转化为3种情况:
- ① 先到达 i-1 位置,然后跨一阶到达 i;
- ② 先到达 i-2 位置,然后一次性跨两阶到达 i;
- ③ 先到达 i-3 位置,然后一次性跨三阶到达 i
也就是说,我们可以先到达 i-1 或 i-2 或 i-3 位置,再走一步就可以到达i位置。所以到达 i 位置有多少种方法其实就是——到达前 i 的三个位置的方法数的和。
所以可以得出状态转移方程为:
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
;
3. 初始化dp表
从状态转移方程中,我们发现当 i < 3 时,方程是失效的,因为会出现 dp 表的下标为负数的情况。所以,也就是说,当 i = 0,i = 1,i = 2 时,不能依靠状态转移方程推出 dp[i] 的值,则需要我们手动进行初始化,即:
dp[1] = 1
,dp[2] = 2
,dp[3] = 4(i=0时无意义)
;
4. 填表顺序
- 本题中,我们发现要想推导出当前位置的值,首先要知道前面 3 个 dp 值,所以,填表顺序为
从左往右
。
5. 返回值
- dp[i] 的含义为到达第 i 个台阶有多少种方法,所以返回值为
dp[n]
;
代码编写
C++
class Solution {
const int MOD = 1e9 + 7; // 用来取模
public:
int waysToStep(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
if(n == 3) return 4;
// 创建dp表
vector<int> dp(n+1);
// 初始化
dp[1] = 1; dp[2] = 2; dp[3] = 4;
// 填表
for(int i = 4; i <= n; i++)
dp[i] = ((dp[i-1] + dp[i-2]) % MOD + dp[i-3]) % MOD;
// 返回
return dp[n];
}
};
JAVA
class Solution
{
public int waysToStep(int n)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回值
int MOD = (int)1e9 + 7;
// 处理⼀下边界情况
if(n == 1 || n == 2) return n;
if(n == 3) return 4;
int[] dp = new int[n + 1];
dp[1] = 1; dp[2] = 2; dp[3] = 4;
for(int i = 4; i <= n; i++)
dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;
return dp[n];
}
}
3.使用最小花费爬楼梯
-
题目链接:使用最小花费爬楼梯
-
题目描述:
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
解题流程
1. 状态表示
dp[i]:到达第 i 个台阶的最小花费
;
2. 状态转移方程
由题可知,到达第 i 个台阶,有两种方式:
- ① 从 i-1 的位置到达 i,花费为
dp[i-1] + cost[i-1]
; - ② 从 i-2 的位置到达 i,花费为
dp[i-2] + cost[i-2]
;
解释:要想计算 i-1 到达 i 的最小花费,首先要保证到达 i-1 时,所用的花费为最小花费,最小花费为 dp[i-1] 。其次,要想从 i-1 位置到达 i,还得加上 i-1 台阶需要交付的费用,即 cost[i-1]。i-2 同理。
所以,到达i有两种方式,我们选择两种方式中,花费最小的那个,所以需要比较,即 min(方式1,方式2)。所以我们可以得出状态转移方程为:
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
;
3. 初始化dp表
题目上说,你可以从0或1位置开始爬,所以意味着到达0和1位置是免费的,即:
dp[0] = 0
;dp[1] = 0
;
4. 填表顺序
- 本题中,我们发现要想推导出当前位置的值,首先要知道前面2个dp值,所以,填表顺序为
从左往右
。
5. 返回值
- 有状态表示可知,应返回
dp[n]
的值;
代码编写
C++
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
// 创建一个dp表
vector<int> dp(n+1);
// 初始化
dp[0] = 0; dp[1] = 0;
// 填表
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];
}
};
JAVA
class Solution
{
public int minCostClimbingStairs(int[] cost)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回值
int n = cost.length;
int[] dp = new int[n + 1];
for(int i = 2; i <= n; i++)
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
return dp[n];
}
}
4.解码方法(中等)
-
题目链接:解码方法
-
题目描述:
一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> “1”
‘B’ -> “2”
…
‘Z’ -> “26”
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:
“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
解题流程
1. 状态表示
dp[i]:在字符串区间[0,i]上共有dp[i]种解码方法
;
2. 状态转移方程
对于i位置有多少种解码方法,可从两方面来看:
- ① i 位置可以单独解码成功;
- ② i 位置与 i-1 位置组合成一个字母解码成功;
对上面两种情况单独进行分析,每种情况又可以分为两种情况:
对于单独解码的情况
- Ⅰ. 解码成功:如果 s[i] 不等于零,即可解码成功 (因为 0 没有对应的编码)。此时 dp[i] 就等于 dp[i-1] ,因为 i 可以单独解码,不会影响总的方法,例如 [9] [9,6] ,加入了 6 之后并不会影响编码结果;
- Ⅱ. 解码失败:如果 s[i] 等于 0 ,解码失败,dp[i] = 0;因为一个字符串中,当你以某种方法进行解码,留下了最后一个数字是个 0,解码失败了,证明你的方法是错的。例如下面这个字符串就会解码失败。
对于组合解码的情况
- Ⅰ. 解码成功:如果 i 位置与 i-1 位置组合起来是 [10,26] 的数字,即解码成功,此时 dp[i] = dp[i-2],原因同上;
- Ⅱ. 解码失败:如果两位组合是 ”06“、”09“ 这样的数字,即为解码失败,若大于 26,也为失败,此时 dp[i]=0。
综上所述,我们可以得到状态转移方程为:
- 当 s[i] 上的数在[1, 9] 区间上时:
dp[i] += dp[i - 1]
; - 当 s[i - 1] 与 s[i] 上的数结合后,在 [10, 26] 之间的时候:
dp[i] +=dp[i - 2]
;
3. 初始化dp表
方法一 直接初始化
与之前的情况相同,由于需要用到 dp[i-1],dp[i-2] 的值,所以i需大于等于 2,dp[0],dp[1] 的值需要手动赋值。
如果 s[0] 等单独解码成功
,则dp[0] = 1
,否则dp[0] = 0
;如果 s[1] 能单独解码成功
,则dp[1] += dp[0]
;如果 s[1] 与 s[0] 组合解码成功
,则dp[1] += 1
;
方法二 辅助位初始化
可以在最前⾯加上⼀个辅助结点,帮助我们初始化。使⽤这种技巧要注意两个点:
辅助结点里面的值要保证后续填表是正确的
;下标的映射关系
;
4. 填表顺序
- 本题中,我们发现要想推导出当前位置的值,首先要知道前面2个 dp 值,所以,填表顺序为
从左往右
。
5. 返回值
- 方法一:应该返回
dp[n - 1]
的值,表⽰在 [0, n - 1] 区间上的编码方法。 - 方法二:由于多增加了一个辅助位,所以返回值要
后移一位
;
代码编写
C++ 方法一
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
// 创建一个dp表
vector<int> dp(n);
// 初始化
dp[0] = s[0] != '0';
// 处理边界情况
if (n == 1) return dp[0];
if (s[1] <= '9' && s[1] >= '1') dp[1] += dp[0];
int t = (s[0] - '0') * 10 + (s[1] - '0');
if (t <= 26 && t >= 10) dp[1] += 1;
// 填表
for (int i = 2; i < n; i++)
{
if (s[i] == '0') dp[i] = 0;
if (s[i] <= '9' && s[i] >= '1') dp[i] += dp[i - 1];
int t = (s[i - 1] - '0') * 10 + (s[i] - '0');
if (t <= 26 && t >= 10) dp[i] += dp[i - 2];
}
// 返回
return dp[n - 1];
}
};
C++ 方法二
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
// 创建一个dp表
vector<int> dp(n+1);
// 初始化
dp[0] = 1; // 辅助位
dp[1] = s[0] != '0';
// 填表
for(int i = 2; i <= n; i++)
{
if(s[i] <= '9' && s[i] >= '1') dp[i] += dp[i-1];
int t = (s[i-1] - '0')* 10 + (s[i] - '0');
if(t <= 26 && t >= 10) dp[i] += dp[i-2];
}
// 返回
return dp[n-1];
}
};
JAVA
class Solution
{
public int numDecodings(String ss)
{
// 1. 创建 dp 表
// 2. 初始化
// 3. 填表
// 4. 返回值
int n = ss.length();
char[] s = ss.toCharArray();
int[] dp = new int[n + 1];
dp[0] = 1; // 保证后续填表是正确的
if(s[1 - 1] != '0') dp[1] = 1;
for(int i = 2; i <= n; i++)
{
// 先处理第⼀种情况
if(s[i - 1] != '0') dp[i] += dp[i - 1];
// 处理第⼆种情况
int tt = (s[i - 2] - '0') * 10 + s[i - 1] - '0';
if(tt >= 10 && tt <= 26) dp[i] += dp[i - 2];
}
return dp[n];
}
}