递归到动归的一般转换
递归函数有n个参数,就定义一个n维数组,数组的下标是参数的取值范围,数组元素
的值是递归函数的返回值,这样就可以从边界开始,逐步填充数组
动规的解题步骤
1,把原问题分解为子问题,子问题的解一旦求出就会被保存,所以每个子问题只需要求一次
2,确定状态,一个状态值就是一个或多个子问题的解
3,确定一些初始状态(边界状态的值),我们可以简单地额计算或者一眼就可以看出来的边界状态值
4,状态转移方程:即如何从一个或多个“值”已知的“状态”,
求出另一个状态的值,即“人人为我”递推型。
(问题具有最优子结构性质,无后效性的问题才能用动规)
动态规划常用的两种形式
1,递归型:直观,容易编写,可以使用滚动数组(比如说,第一步求出的值存在一个数组中,第二步求出的值后已经不需要第一步求出的了,可以直接覆盖之前的,还是存在同一个数组内)
2,递推型,效率高,可以避免函数调用,快一些,但是我觉得这样看的不清楚qwq
1,数字三角形问题
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所有经过的数字之和最大。
路径上的每一步都只能往左下或者右下走。只需要求出这个最大和即可,不必给出具体路径。
三角形行数大于1小于等于100,数字为0-99.
#include
#include
#include
using namespace std;
//记忆型递归型动态规划解决数字三角形问题
#define MAX 101
int D[MAX][MAX]; //保存数字三角形里的值
int maxsum[MAX][MAX]; //初始化为全部为-1,保存i,j位置上的最大和
int n; //数字三角形的行数
int MaxSum(int i, int j)
{
if(maxsum[i][j] != -1)
return maxsum[i][j];
if(i == n)
maxsum[i][j] = D[i][j];
else
{
maxsum[i][j] = max(maxsum[i+1][j], maxsum[i+1][j+1]) + D[i][j];
}
}
int main()
{
cin >> n;
int i, j;
for(i = 1; i <= n; i++)
for(j = 0; j <= i; j++)
{
maxsum[i][j] = -1;
cin >> D[i][j];
}
cout << MaxSum(1,1) << endl;
}
还是很好理解的对吧。
还可以用循环的方式实现递推
//用循环实现递推,值需要一个双重循环就够了
int main()
{
cin >> n;
int i, j;
for(i = 1; i <=n ;i++)
for(j = 1; j <= i ; j++)
{
cin >> D[i][j];
}
//先初始化最底下一层
for(j = 1; j <= n; j++)
maxsum[n][j] = D[n][j];
//从下往上的过程
for(i = n-1; i >= 1; i--)
for(j = 1; j <= i; j++)
{
maxsum[i][j] = max(maxsum[i+1][j], maxsum[i+1][j+1]) + D[i][j];
}
cout << maxsum[1][1] << endl;
}
2,最长上升子序列(题目自行百度),这个题目的子问题是以ak为结尾的最长子序列的长度,存放在maxlen中
#define MAX 100
int D[MAX];
int maxlen[MAX];
int n;
//以ax为结尾的最长的子序列的长度
int main()
{
cin >> n;
int i, j;
for(i = 0; i < n; i++)
{
cin >> D[i];
maxlen[i] = 1;
}
int ans = 0;
for(i = 1; i < n; i++)
{
for(j = 0; j < i; j++)
{
if(D[i] > D[j]) //这一步我真的是蠢到爆炸,写成了maxlen[i] > maxlen[j]
maxlen[i] = max(maxlen[i], maxlen[j] + 1);
}
ans = max(ans, maxlen[i]);
}
cout << ans << endl;
}
3,给出两个字符串,求出这样的一个最长公共子序列的长度:
子序列中的每一个字符都能在原来的串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。
//最长公共子序列
//动态规划的题目还是要具体问题具体分析,但是还是有一定的套路可寻的
#define MAX 100
int dp[MAX][MAX]; //dp[i][j]存放s1左边i个字母和s2左边j个字母的最长公共子列的长度
char s1[MAX];
char s2[MAX];
int main()
{
int n;
cin >> n;
int i, j;
for(i = 0; i < n; i++)
{
cin >> s1[i];
}
for(i = 0; i < n; i++)
{
cin >> s2[i];
}
//确定边界
//dp[i][0]和dp[0][i]都是等于0
for(i = 0; i < n; i++)
{
dp[0][i] = 0;
dp[i][0] = 0;
}
//dp可以想象成一个横竖的表,第一行和第一列已经是作为边界已知的为0了
//然后接下来就是要用已知的边界值去填充未知的dp
//按照状态转换方程来填充啦~
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
{
if(s1[i] == s2[j])
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][n] << endl;
}