参考文章:动态规划入门闫氏DP分析法,从此再也不怕DP问题!_哔哩哔哩_bilibili
动态规划 就是 : 给定一个问题,我们把它拆成一个个子问题 ,直到子问题可以直接解决。然后把子问题的答案保存起来,以减少重复计算(记忆化搜索)。再根据子问题答案反推,得出原问题解的一种方法.
主要方法:
1.设计一个数据结构,记录不同规模问题的答案
2.数据结构采用从小到大的生成方式去生成
一般流程: 1.大问题到小问题的拆分 2.找到最小问题 3.记录最小问题,回推到大问题
动态规划入门思路: dfs暴力 --- 记忆化搜索 --- 递推
1dfs > 2记忆化搜索 > 3逆序递推 > 4顺序递推 > 5优化空间
记忆化搜索 == dfs暴力+记忆存储重复结果
递推 == dfs的向下递归的公式 递推的边界为:递归的边界
题目:821. 跳台阶 - AcWing题库
cpp
#include<bits/stdc++.h>
using namespace std;
const int N= 100;
int n;
int f[N];
//递推公式
int main(){
scanf("%d",&n);
f[1]=1,f[2] =2;
if(n==1||n==2){
f[1]=1,f[2]=2;
printf("%d\n",f[n]);
return 0;
}
for(int i = 3;i<=n;i++){
f[i] = f[i-1]+f[i-2];
}
printf("%d\n",f[n]);
return 0;
}//dp递推
2.分苹果3428. 放苹果 - AcWing题库
思路:动态规划
把n 个苹果放到m 个盘子当中,去除重合的情况
数组f[i][j] 表示 把i个苹果放到j个盘子当中 f[0][0] = 1,表示空拆分,递推的起点
分情况:
1.如果i>=j(也就是苹果的数量大于盘子数量) 则: 先把每个盘子都放一个苹果 ,然后剩下i-j个苹果在放随便放j个盘子当中 f[i][j] = f[i][j-1] +f[i-j][j]; j-1表示方案数减1,f[i-j][j]表示还剩下i-j个元素, 有j种方案;
2.如果苹果数量少于盘子数量 ,那么就只有苹果数量这么多种方法 f[i][j] = f[i][i]
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 15;
int f[N][N];//全局定义数组,不用初始化
int main(){
int n,m;
while(cin>>n>>m){
f[0][0]=1;//不进行拆分也是一种方案,是所有分拆方案的 "起点" 和 "边界条件"
// memset(f,1,sizeof f);
for(int i = 0;i<=n;i++){
for(int j =0;j<=m;j++){
if(i>=j)
f[i][j] = f[i][j-1]+f[i-j][j];//当苹果的数量大于盘中的时候
//可以把每个盘子都放一遍, 剩下的苹果可以放任意的j个盘子
else
f[i][j] = f[i][i];//苹果小于盘子时,只能放苹果数量这么多方案
}
}
printf("%d\n",f[n][m]);//解决把n个苹果放到m个盘子中的方案数量
}
return 0;
}

最长公共子序列长度(子序列不要求字符连续,但顺序必须一致)
核心思路:动态规划的状态定义和状态转移
**1.**dp[i][j] 表示 s1 字符串前i个字符和s2字符串前j个字符的最长公共子序列的长度
2.二维数组初始化空串和任何字符串的公共子序列只能是空串,长度自然为 0
for(int i = 0;i<=n;i++) dp[i][0] = 0; // s2前0个字符(空串),和任意s1前i个字符的LCS长度为0 for(int j = 0;j<=m;j++) dp[0][j] = 0; // s1前0个字符(空串),和任意s2前j个字符的LCS长度为0
3.状态转移:分两种情况:1.字符匹配相等(dp[i][j] = dp[i-1][j-1]+1;因为字符前i-1和j-1个相匹配,后面进行,在原基础上长度加1 ), 2. 字符匹配不相等(不能直接加上去,而是去除s1,s2的当前最大值的字符)
cpp
if(s1[i-1]==s2[j-1])
dp[i][j] = dp[i-1][j-1]+1; // 字符相等的情况
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]); // 字符不相等的情况
4.dp[n][m] 长度为n的字符串s1和长度为m的字符串s2的长度的最长公共子序列的长度大小
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1020;
int dp[N][N];
int n,m;
char s1[N],s2[N];
int main(){
cin>>n>>m;
cin>>s1>>s2;
//二维数组初始化
for(int i = 0;i<=n;i++)
dp[i][0] = 0;
for(int j = 0;j<=m;j++)
dp[0][j] = 0;
//匹配的条件
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(s1[i-1]==s2[j-1])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
//打印
cout<<dp[n][m];
return 0;
}
附件问题: 然后输出最短公共子序列是什么:
思路:定义一个lcs字符串 进行拼接 如果s1 == s2 则拼接,并且i--,j--,如果不相等 则 将dp长度大的减1, 最后将lcs使用reverse反转一下
cpp
//逻辑实现 从子串后面往前遍历 ,最后做一个反转reverse
int i = n,j=m;
while(i>0&&j>0){
if(s1[i-1]==s2[j-1]){
LCA+=s1[i]-1;
i--;j--;
}
else if(dp[i-1][j]>dp[i][j-1])//dp长度比较 ,如果i的长度大,则减1 ,否则 j 减1
i--;
else
j--;
}
reverse(LCS.begin(), LCS.end());
cout<<"最长公共子序列长度"<<dp[n][m]<<endl;
cout<<"最长公共子序列为:"<<LCS;
2.求解最长公共子串3508. 最长公共子串 - AcWing题库


使用二维数组空间大 则使用一维数组替换的核心思路:"复用" 空间
既然计算 dp[i][j] 只需要 dp[i-1][j-1],我们可以用一维数组 dp[j] 来 "复用" 存储
- 初始时,
dp[j]存储的是上一行(i-1 行) 的所有结果; - 计算当前行(i 行)的
dp[j]时,我们需要的是上一行的dp[j-1](即原dp[i-1][j-1]);
cpp
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cctype> //isalpha 的头文件
using namespace std;
const int N = 10010;
int dp[N];
char s1[N],s2[N];
int curmax = 0;
int main(){
cin>>s1>>s2;
int s1len = strlen(s1);
int s2len = strlen(s2);
memset(dp,0,sizeof dp);//初始化
for(int i =1 ;i<=s1len;i++){
for(int j = s2len;j>0;j--){//倒序输出
if(s1[i-1]==s2[j-1]&&isalpha(s1[i-1])&&isalpha(s2[j-1])){
dp[j] =dp[j-1]+1;//用一维数组记录
curmax = max(dp[j],curmax);//取最大值
}
else
dp[j] = 0;
}
}
printf("%d\n",curmax);
return 0;
}
思路:
dp 情况:
case1 :
如果 d[i-1] <=0 则下一位d[i] = s[i-1]返回是一个位置'
case2
如果 d[i-1]>0 则下一位d[i] = s[i-1] +d[i-1]直接加上其和
使用curmax 进行存放每次的最大值
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const long long N = 100000010;
long long dp[N];//dp[i] 表示以s[i-1]为结尾的连续子数组的最大和
int n;
long long s[N];
int main(){
scanf("%d",&n);
for(int i = 0;i<n;i++)
scanf("%lld",&s[i]);//输入数字
dp[1] = s[0];//dp[1] 以s[0](第一个元素)结尾的最大子数组和就是它本
long long curmax = dp[1];//初始化
//状态转移
for(int i = 2;i<=n;i++){
if(dp[i-1]<=0)
//case1 dp[i-1]<=0 会拉低和的值,不如重新开始
dp[i] = s[i-1];
else
dp[i] = s[i-1] + dp[i-1];//接上它能让当前和更大,直接累加
curmax = max(curmax,dp[i]);
}
cout<<curmax;
return 0;
}
4.最大上升子序列和最大上升子序列和_牛客题霸_牛客网
思路:
dp状态转移条件,dp数组是以num[i]为结尾的数组,前面上升序列和
if(num[i]>num[j])
dp[i] = max(dp[i],dp[j]+num[i]);//记录更新大小的值 dp[j] + num[i]表示接上这个数之后,
在所有能接的 j 里,选一个让 dp[i] 最大的结果,保证 dp[i] 始终是 "以 num[i] 结尾的最大和"。
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1010;
int dp[N];//dp记录
int num[N];
int n;
int main(){
while(cin>>n){
for(int i = 0;i<n;i++)
scanf("%d",&num[i]);//初始化数组
int ans= 0; //初始ans
for(int i = 0;i<n;i++){//遍历循环
dp[i] = num[i];//初始化
for(int j = 0;j<i;j++){
if(num[i]>num[j]){//判断条件
dp[i] = max(dp[i],dp[j]+num[i]);//记录更新大小的值
}
ans = max(ans,dp[i]); //更新最值
}
}
cout<<ans<<endl;
}
return 0;
}