一、经验总结
动态规划题型的解题思路:
- 状态表示:dp[i]的含义是什么。通过解题经验和题目要求得到,一般有以下两个方向:
- 以i位置为起点
- 以i位置为终点
- 状态转移方程:dp[i]怎么求。根据距离i位置最近的一步划分问题,用之前或之后的状态推导出dp[i]的值。
- 初始化:为了保证填表的时候不越界,预处理边界情况
- 填表顺序:保证填写当前状态的时候,所需要的状态已经计算过了
- 返回值:题目要求+状态表示
提示:同一个动态规划问题,有时可以采用多种不同的状态表示(如2.3)。状态表示不同,其余的4点也不同。而有些问题的状态表示可能只适用于其中的一种,要因题而议选择可行的方案。
动态规划的优化方案:
- 空间优化:采用滚动数组的方案,只需要记录求当前状态时所需要的状态即可。
- 虚拟节点:设置虚拟节点,方便处理边界情况。需要注意以下两点:
- 虚拟节点的初始值,要保证后续填表的正确性。
- dp表与原始表的下标映射关系
二、相关编程题
2.1 第N个泰波那契数
题目链接
1137. 第 N 个泰波那契数 - 力扣(LeetCode)
题目描述
算法原理
编写代码
//动态规划
class Solution {
public:
int tribonacci(int n) {
vector<int> dp(n<=2? 3:n+1); //创建dp表
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]; //返回值
}
};
//空间优化
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, d;
for(int i = 3; i <= n; ++i)
{
d = a+b+c;
a=b, b=c, c=d; //滚动数组
}
return d;
}
};
2.2 三步问题
题目链接
面试题 08.01. 三步问题 - 力扣(LeetCode)
题目描述
算法原理
编写代码
//经典动态规划
class Solution {
public:
int waysToStep(int n) {
const int MOD = 1e9+7;
vector<long> dp(n<=3? 4:n+1); //创建dp表
dp[1]=1, dp[2]=2, dp[3]=4; //初始化
for(int i = 4; i <= n; ++i) //填表
{
dp[i] = dp[i-1]+dp[i-2]+dp[i-3];
dp[i]%=MOD;
}
return dp[n]; //返回
}
};
//空间优化版本
class Solution {
public:
int waysToStep(int n) {
if(n==1 || n==2) return n;
if(n==3) return 4;
const int MOD = 1e9+7;
long a=1, b=2, c=4, d;
for(int i = 4; i <= n; ++i)
{
d = a+b+c;
d %= MOD;
a=b, b=c, c=d; //滚动数组
}
return d;
}
};
2.3 使用最小花费爬楼梯
题目链接
746. 使用最小花费爬楼梯 - 力扣(LeetCode)
题目描述
算法原理
编写代码
//解法一:以i位置为终点
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n+1); //创建dp表
dp[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]; //返回值
}
};
//解法二:以i位置为起点
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n); //创建dp表
dp[n-1] = cost[n-1]; //初始化
dp[n-2] = cost[n-2];
for(int i = n-3; i >= 0; --i) //填表
{
dp[i] = min(dp[i+1], dp[i+2]) + cost[i];
}
return min(dp[0], dp[1]); //返回值
}
};
2.4 解码方法
题目链接
91. 解码方法 - 力扣(LeetCode)
题目描述
算法原理
编写代码
//经典动态规划
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
if(n==1) return CheckCode(s[0])? 1:0;
vector<int> dp(n); //dp[i]表示以i位置为结尾的解码方法
dp[0] = CheckCode(s[0])? 1:0; //初始化
dp[1] = CheckCode(s[1])? dp[0]:0;
dp[1] += CheckCode(s[0], s[1])? 1:0;
for(int i = 2; i < n; ++i)
{
dp[i] = CheckCode(s[i])? dp[i-1]:0;
dp[i] += CheckCode(s[i-1], s[i])? dp[i-2]:0;
}
return dp[n-1];
}
bool CheckCode(char a, char b = 0)
{
if(b == 0)
{
return a>='1' && a<='9';
}
else
{
int tmp = (a-'0')*10+b-'0';
return tmp>=10 && tmp<=26;
}
}
};
//添加虚拟节点,方便处理边界问题
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
vector<int> dp(n+1); //dp[i]表示以i位置为结尾的解法方法
dp[0] = 1; //注意虚拟节点的初始值
dp[1] = CheckCode(s[0])? 1:0; //初始化
for(int i = 2; i <= n; ++i)
{
dp[i] = CheckCode(s[i-1])? dp[i-1]:0;
dp[i] += CheckCode(s[i-2], s[i-1])? dp[i-2]:0;
}
return dp[n];
}
};