做动态规划的题目,题目有个固定的模式:
那么怎么确定状态表示呢? (1)题目要求 (2)经验+题目要求 (3)分析问题过程中发现重复子问题
dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
每一道题目的状态转移方程是不一样的。
写代码的四大部分: 1.创建dp表 2.初始化 3.填表 4.确定返回值
将题目给的条件Tn+3 = Tn + Tn+1 + Tn+2同时减去3,就能得到下面这个式子,也就是说第n个等于它前面三项之和。
1.创建dp表
先根据题目要求先创建dp表,这里要包含n个数,就建一个n+1大小的dp表:vector<int> dp(n+1)
2.初始化
根据题目已给的定义初始化 dp[0]=0;dp[1]=dp[2]=1;
3.填表
在循环里面根据题目已给的公式写出循环 dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
4.确定返回值
最后返回得到的结果return dp[n];
处理边界问题: 这里考虑到可能会越界,就得先加一个判断:
if(n==0) return 0;
if(n==1||n==2) return 1;
这个代码空间复杂度为O(n),优化一下代码,将空间复杂度降到O(1)。
写出几项就会发现,将设置的几个变量连续赋值,就能达到滚动的效果。将b的值先赋给a,在c的值先赋给b,d的值先赋给c。就这样一直到n,最后返回d的值就行。
class Solution {
public:
int tribonacci(int n) {
if(n==0) return 0;
if(n==1||n==2) return 1;
vector<int> dp(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];
}
};
优化空间后的代码:
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=0;
for(int i=3;i<=n;i++)
{
d=a+b+c;
a=b;
b=c;
c=d;
}
return d;
}
};
假设要到第4个台阶,就可以从第3个台阶到第4个台阶,也可以从第2个台阶到第4个台阶,还可以从第1个台阶到到第4个台阶,总的到第第4个台阶的方法也就是上面加的和。
那么很显然,要到第i个台阶,知道到第i-1和i-2和i-3有多少种就可以了:
dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
dp[1]=1;dp[2]=2;dp[3]=4;
if(n==1||n==2)return n;
if(n==3)return 4;
题目还要求取模,就直接定义一个int const Mod=1e9+7;
用来取模。
最后返回return dp[n];
就行。
编写代码: 1.创建dp表 2.初始化 3.填表 4.确定返回值
class Solution {
public:
int waysToStep(int n) {
int const Mod=1e9+7;
if(n==1||n==2)return n;
if(n==3)return 4;
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];
}
};
楼顶在哪里? 题目所给的数组都是楼梯,当把楼梯都跨过去才是楼顶,也就是数组最后一个位置的下一个位置才是楼顶。
这里得先明白到达楼梯顶部不是这里顺序表的长度,而是长度再加1。
这里选择的是对应花费最小的那一个再加上它对应dp表所对应的值:
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
dp[0]=dp[1]=0;
return dp[n];
支付完i位置走到i+1位置,再到终点 支付完i位置走到i+2位置,再到终点
状态转移方程就是:
dp[i]=min(dp[i+1],dp[i+2])+cost[i];
此时初始化的位置就是n-1和n-2:
dp[n-1]=cost[n-1];
dp[n-2]=cost[n-2];
return min(dp[0],dp[1]);
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n=cost.size();
vector<int> dp(n+1);
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];
}
};
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n=cost.size();
vector<int> dp(n);
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]);
}
};
题目所述就是把一串数字反向解码为字母映射出来,有多少种方法。 题目也说,一个单独的数字可以映射的,但是这个数字前面是0的话就不可以。
来看看题目给的示例: 226就有三种解码方式:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6)
状态表示方程:dp[i]=dp[i-1]+dp[i-2]
,都是成功时候才加。
优化:处理边界以及初始化问题 多开一个虚拟位置,有什么用呢? 在旧的初始化列表中,初始化dp[1]是比较麻烦的,如果把它放在填表位置就会比较轻松。
得注意:1. 保证虚拟节点位置值是正确的;2.得注意下标映射关系 当要在新的dp表里面2的结果就要用到0和1位置的值。这里dp[0]=1,要想在2位置解码成功,那么0位置必须是解码成功的。 在新的dp表里面的i统一都对应把位置往后面移动了一位,这里在写代码的时候就得减1。
class Solution {
public:
int numDecodings(string s) {
int n=s.size();
vector<int> dp(n);
dp[0]=s[0]!='0';
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++)
{
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];
}
};
优化后:
class Solution {
public:
int numDecodings(string s) {
int n=s.size();
vector<int> dp(n+1);
dp[0]=1;
dp[1]=s[1-1]!='0';
for(int i=2;i<=n;i++)
{
if(s[i-1]!='0')dp[i]+=dp[i-1];
int t=(s[i-2]-'0')*10+s[i-1]-'0';
if(t>=10&&t<=26) dp[i]+=dp[i-2];
}
return dp[n];
}
};
有问题请指出,大家一起进步!!!