题目如下
解题思路
解法1:动态规划
动态规划回顾
- 什么是动态规划?
- 动态规划的核心思想
- 一个例子走进动态规划
- 动态规划的解题套路
动态规划的含义:
动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
★ dynamic programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems.”以上定义来自维基百科,看定义感觉还是有点抽象。简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。
★ 一般这些子问题很相似,可以通过函数关系式递推出来。然后呢,动态规划就致力于解决每个子问题一次,减少重复计算,比如斐波那契数列就可以看做入门级的经典动态规划问题。”核心思想:动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
我们来看下,网上比较流行的一个例子:
A : "1+1+1+1+1+1+1+1 =?"
A : "上面等式的值是多少"
B : 计算 "8"
A : 在上面等式的左边写上 "1+" 呢?
A : "此时等式的值为多少"
B : 很快得出答案 "9"
A : "你怎么这么快就知道答案了"
A : "只要在8的基础上加1就行了"
A : "所以你不用重新计算,因为你记住了第一个等式的值为8!动态规划算法也可以说是 '记住求过的解来节省时间'"动态规划的解题套路
什么样的问题可以考虑使用动态规划解决呢?
★ 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。”比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
动态规划的解题思路
动态规划的核心思想就是拆分子问题,记住过往,减少重复计算。 并且动态规划一般都是自底向上的,因此到这里,基于青蛙跳阶问题,我总结了一下我做动态规划的思路:
- 穷举分析
- 确定边界
- 找出规律,确定最优子结构
- 写出状态转移方程
代码
dp[0][0][...] = 边界值 for(状态1 :所有状态1的值){ for(状态2 :所有状态2的值){ for(...){ //状态转移方程 dp[状态1][状态2][...] = 求最值 } } }
参考文献:
思路和算法
即只考虑在前一个情况“已有的公共序列”基础上的变化。
step1:递归公式(第一步总的先来构建dp矩阵)
- 当X(Y)至少有一个序列为空时:
此时,最短公共超序列z一定要包含X(Y)全部字符,而求最短公共超序列,于是最短公共超序列就是X(Y)本身;(代码中即是先初始化0行0列)
- Y为空时:dp(i,0)=i (1<=i<=m)
- X为空时:dp(0,j)=j (1<=j<=n)
- 当X[m]=Y[n]时:
此时,该字母一定包含在最短公共超序列中,相等就只需要加一个就好了。(比如x中是a,y中也是a,最短公共超序列中只需要加一个a就行了);
- dp(m,n)= dp(m-1,n-1)+1
- 当X[m] != Y[n]时:
当x[m](y[n])为最短公共超序列中需要新添加的一个字符时(x出现了一个从来没出来的字符);(注:下面的情况1和情况2都可计算出可能出现的超序列,但是为了最终选择最短超序列,于是选择他们中的最小者,进行当作这一步的基础)
- dp[i][j] = dp[i-1][j] + 1
- dp[i][j] = dp[i][j-1] + 1
- 举例:str1=AABXFGA,str2=ABAAXGF;
step2:待整理?开始在dp矩阵中选择执行路线作为最终的执行地图,即序列;看代码可以理解为反向计算取出,dp[i][j]是由谁算出来的,于是便判断新增加进公共超序列的字符来自于谁(逆向计算该位置减一是得到上方还是左方,上方则来自于横轴序列,左方则来自于纵轴序列,来自横轴序列时,则向上一行,相当于改变纵轴序列,毕竟纵轴序列没有影响当前的这个新增的字符。如果a[i]和b[j]相同,则该位置计算斜上方的那个[i-1][j-1]),然后把新的字符加进来~
代码1
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
if(a>b)
return a;
else return b;
}
int min(int a,int b)
{
if(a<b)
return a;
else return b;
}
int dp[105][105],len1,len2,cou=0;
char a[105],b[105],c[105]; //a,b用来存放输入的两个字符串,c存放最短公共超序列的一种解
int main()
{ int i,j;
scanf("%s",&a);
scanf("%s",&b);
len1=strlen(a);
len2=strlen(b);
for(i=strlen(a);i>0;i--) //为了方便dp数组的操作,我将字符往后移动一位
a[i]=a[i-1];
for(i=strlen(b);i>0;i--)
b[i]=b[i-1];
a[0]='0'; //随便给的值
b[0]='1';
for(i=0;i<=max(len1,len2);i++) //当某个字符串为空时,赋初值
dp[i][0]=dp[0][i]=i;
for(i=1;i<=len1;i++)
{
for(j=1;j<=len2;j++)
{
if(a[i]==b[j])
dp[i][j]=dp[i-1][j-1]+1; //根据递推公式求dp()
else
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;
}
}
i=len1;
j=len2;
//开始选择路径
while(i>0&&j>0) //根据比较最后的值是怎么来的,计算出最短超序列的一种解
{
if(dp[i][j]==dp[i-1][j-1]+1&&a[i]==b[j]) //相等时
{
c[cou]=a[i];
cou++;
i--;
j--;
}
else if(dp[i][j]==dp[i-1][j]+1&&a[i]!=b[j])//来自a
{
c[cou]=a[i];
cou++;
i--;
}
else if(dp[i][j]==dp[i][j-1]+1&&a[i]!=b[j])//来自b
{
c[cou]=b[j];
cou++;
j--;
}
}
while(i>0) //输入的字符串大小不等,或者在a,b中选取的个数不相等时,若第0行或者第0列没有选完时
{
c[cou]=a[i];
i--;
cou++;
}
while(j>0)
{
c[cou]=b[j];
j--;
cou++;
}
for(i=strlen(c)-1;i>=0;i--) //输出其中一个最短超序列
printf("%c",c[i]);
printf("\n");
printf("\n%d",dp[len1][len2]);
return 0;
}
参考文献: